<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>Dubbo 源码分析 – 自适应拓展原理 | 云少IT</title><meta name="keywords" content="Dubbo 源码分析 – 自适应拓展原理"><meta name="author" content="云少"><meta name="copyright" content="云少"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-touch-fullscreen" content="yes"><meta name="apple-mobile-web-app-title" content="Dubbo 源码分析 – 自适应拓展原理"><meta name="application-name" content="Dubbo 源码分析 – 自适应拓展原理"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta property="og:type" content="article"><meta property="og:title" content="Dubbo 源码分析 – 自适应拓展原理"><meta property="og:url" content="https://it985.github.io/posts/cf451fe.html"><meta property="og:site_name" content="云少IT"><meta property="og:description" content="二、Dubbo 源码分析 – 自适应拓展原理1.原理我在上一篇文章中分析了 Dubbo 的 SPI 机制，Dubbo SPI 是 Dubbo 框架的核心。Dubbo 中的很多拓展都是通过 SPI 机制进行加载的，比如 Protocol、Cluster、LoadBalance 等。有时，有些拓展并非想"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://www.bing.com/th?id=OHR.FrankensteinFriday_EN-IN2387604310_UHD.jpg"><meta property="article:author" content="云少"><meta property="article:tag" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://www.bing.com/th?id=OHR.FrankensteinFriday_EN-IN2387604310_UHD.jpg"><meta name="description" content="二、Dubbo 源码分析 – 自适应拓展原理1.原理我在上一篇文章中分析了 Dubbo 的 SPI 机制，Dubbo SPI 是 Dubbo 框架的核心。Dubbo 中的很多拓展都是通过 SPI 机制进行加载的，比如 Protocol、Cluster、LoadBalance 等。有时，有些拓展并非想"><link rel="shortcut icon" href="/img/logo.webp"><link rel="canonical" href="https://it985.github.io/posts/cf451fe"><link rel="preconnect" href="//npm.elemecdn.com"><link rel="preconnect" href="//npm.onmicrosoft.cn"><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""><link rel="preconnect" href="//busuanzi.ibruce.info"><meta name="google-site-verification" content="NuBZ4r-QCqSgo4XUScdEsQW0bolIHEiVGq4A16ndPQA"><meta name="baidu-site-verification" content="code-xxx"><meta name="msvalidate.01" content="xxx"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.cbd.int/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css" media="print" onload='this.media="all"'><script async src="https://www.googletagmanager.com/gtag/js?id=G-3VMKW5TZBM"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-3VMKW5TZBM")</script><script>const GLOBAL_CONFIG={linkPageTop:{enable:!0,title:"与数百名博主无限进步",addFriendPlaceholder:"昵称（请勿包含博客等字样）：\n网站地址（要求博客地址，请勿提交个人主页）：\n头像图片url（请提供尽可能清晰的图片，我会上传到我自己的图床）：\n描述：\n站点截图（可选）：\n"},peoplecanvas:void 0,postHeadAiDescription:{enable:!0,gptName:"云AI",mode:"tianli",switchBtn:!1,btnLink:"https://afdian.net/item/886a79d4db6711eda42a52540025c377",randomNum:3,basicWordCount:1e3,key:"48580d1e3f53ae174a1e",Referer:"https://blog.tryrun.top"},diytitle:{enable:!0,leaveTitle:"w(ﾟДﾟ)w 不要走！再看看嘛！",backTitle:"♪(^∇^*)欢迎肥来！"},LA51:{enable:!0,ck:"JiFOrFoQklEn9YLS",LingQueMonitorID:"JiqlTmdeI4e1fPbd"},greetingBox:{enable:!0,default:"晚上好👋",list:[{greeting:"晚安😴",startTime:0,endTime:5},{greeting:"早上好鸭👋, 祝你一天好心情！",startTime:6,endTime:9},{greeting:"上午好👋, 状态很好，鼓励一下～",startTime:10,endTime:10},{greeting:"11点多啦, 在坚持一下就吃饭啦～",startTime:11,endTime:11},{greeting:"午安👋, 宝贝",startTime:12,endTime:14},{greeting:"🌈充实的一天辛苦啦！",startTime:14,endTime:18},{greeting:"19点喽, 奖励一顿丰盛的大餐吧🍔。",startTime:19,endTime:19},{greeting:"晚上好👋, 在属于自己的时间好好放松😌~",startTime:20,endTime:24}]},twikooEnvId:"https://twikoo.tryrun.top/",commentBarrageConfig:void 0,root:"/",preloader:{source:2},friends_vue_info:{apiurl:"https://friends.tryrun.top/"},navMusic:!1,mainTone:void 0,authorStatus:{skills:["🤖️ 数码科技爱好者","🔍 分享与热心帮助","🏠 智能家居小能手","🔨 设计开发一条龙","🤝 专修交互与设计","🏃 脚踏实地行动派","🧱 团队小组发动机","💢 壮汉人狠话不多"]},algolia:{appId:"T5VW6VDYLS",apiKey:"227bcb041816af13cb1698db15a8ac89",indexName:"hexo-blog",hits:{per_page:6},languages:{input_placeholder:"输入关键词后按下回车查找",hits_empty:"找不到您查询的内容：${query}",hits_stats:"找到 ${hits} 条结果，用时 ${time} 毫秒"}},localSearch:void 0,translate:{defaultEncoding:2,translateDelay:0,msgToTraditionalChinese:"繁",msgToSimplifiedChinese:"简",rightMenuMsgToTraditionalChinese:"转为繁体",rightMenuMsgToSimplifiedChinese:"转为简体"},noticeOutdate:{limitDay:365,position:"top",messagePrev:"It has been",messageNext:"days since the last update, the content of the article may be outdated."},highlight:{plugin:"highlighjs",highlightCopy:!0,highlightLang:!0,highlightHeightLimit:330},copy:{success:"复制成功",error:"复制错误",noSupport:"浏览器不支持"},relativeDate:{homepage:!0,simplehomepage:!1,post:!0},runtime:"天",date_suffix:{just:"刚刚",min:"分钟前",hour:"小时前",day:"天前",month:"个月前"},copyright:void 0,lightbox:"fancybox",Snackbar:{chs_to_cht:"你已切换为繁体",cht_to_chs:"你已切换为简体",day_to_night:"你已切换为深色模式",night_to_day:"你已切换为浅色模式",bgLight:"#425AEF",bgDark:"#1f1f1f",position:"top-center"},source:{justifiedGallery:{js:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.min.js",css:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.css"}},isPhotoFigcaption:!1,islazyload:!0,isAnchor:!1,shortcutKey:void 0,autoDarkmode:!0}</script><script id="config-diff">var GLOBAL_CONFIG_SITE={configTitle:"云少IT",title:"Dubbo 源码分析 – 自适应拓展原理",postAI:"true",pageFillDescription:"二、Dubbo 源码分析 – 自适应拓展原理, 1.原理, 2.源码分析, 2.1 获取自适应拓展, 2.2 自适应拓展类代码生成, 2.2.1 Adaptive 注解检测, 2.2.2 生成类, 2.2.3 生成方法, 2.2.3.1 无 Adaptive 注解方法代码生成, 2.2.3.2 获取 URL 数据, 2.2.3.3 获取 Adaptive 注解值, 2.2.3.4 检测 Invocation 参数, 2.2.3.5 生成拓展名获取逻辑, 2.2.3.6 生成拓展加载与目标方法调用逻辑, 2.2.3.7 生成完整的方法, 3.基于动态代理实现知识与拓展, 4.总结二源码分析自适应拓展原理原理我在上一篇文章中分析了的机制是框架的核心中的很多拓展都是通过机制进行加载的比如等有时有些拓展并非想在框架启动阶段被加载而是希望在拓展方法被调用时根据运行时参数进行加载这听起来有些矛盾拓展未被加载那么拓展方法就无法被调用静态方法除外拓展方法未被调用就无法进行加载这似乎是个死结不过好在也有相应的解决办法通过代理模式就可以解决这个问题这里我们将具有代理功能的拓展称之为自适应拓展并未直接通过代理模式实现自适应拓展而是代理代理模式基础上封装了一个更炫的实现方式首先会为拓展接口生成具有代理功能的代码然后通过或编译这段代码得到类最后在通过反射创建代理类整个过程比较复杂炫丽如此复杂的过程最终的目的是为拓展生成代理对象但实际上每个代理对象的代理逻辑基本一致均是从中获取欲加载实现类的名称因此我们完全可以把代理逻辑抽出来并通过动态代理的方式实现自适应拓展这样做的好处显而易见方便维护也方便源码学习者学习和调试代码本文将在随后实现一个动态代理版的自适应拓展有兴趣的同学可以继续往下读接下来我们通过一个示例演示自适应拓展类这个示例取自官方文档我这里进行了一定的拓展这是一个与汽车相关的例子我们有一个车轮制造厂接口接口的实现类如下从中获取名称通过加载具体的调用目标方法是一个代理类它主要做了三件事情从中获取名称通过加载具体的调用目标方法接下来我们来看看汽车制造厂接口与其实现类通过注入持有一个类型从成员变量在程序启动时我们可以将通过方法注入到中在运行时假设有这样一个类型的参数的方法将上面的作为参数传给的方法方法从中提取参数得到之后再通过加载名为的实现类得到具体的实例上面这个示例展示了自适应拓展类的核心实现在组件方法被调用时通过代理的方式加载指定的实现类并调用被代理的方法经过以上说明大家应该搞懂了自适应拓展的原理接下来我们深入到源码中探索自适应拓展生成的过程源码分析在对自适应拓展生成过程进行深入分析之前我们先来看一下与自适应拓展息息相关的一个注解即注解该注解的定义如下从上面的代码中可知可注解在类或方法上注解在类上时不会为该类生成代理类注解上方法接口方法上时会为为该方法生成代理逻辑注解在类上的情况很少在中仅有两个类被注解了分别是和此种情况表示拓展的加载逻辑由人工编码完成更多时候是注解在接口方法上的表示拓展的加载逻辑需由框架自动生成注解的地方不同相应的处理逻辑也是不同的注解在类上时处理逻辑比较简单本文就不分析了注解在接口方法上时处理逻辑较为复杂本章将会重点分析此块逻辑接下来我们从方法进行分析代码如下获取自适应拓展从缓存中获取自适应拓展缓存未命中创建自适应拓展设置拓展到缓存中方法首先会检查缓存缓存未命中则调用方法创建自适应拓展下面我们看一下方法的代码获取自适应拓展类并通过反射实例化方法代码比较少但却包含了三个动作分别如下调用方法获取自适应拓展对象通过反射进行实例化调用方法向拓展实例中注入依赖前两个动作比较好理解第三个动作不好理解这里简单说明一下方法通过方法向目标对象中注入依赖可以看做是一个简单的实现前面说过中有两种类型的自适应拓展一种是手工编码的一种是自动生成的手工编码的拓展中可能存在着一些依赖而自动生成的拓展则不会依赖其他类这里调用方法的目的是为手工编码的自适应拓展注入依赖这一点需要大家注意一下关于方法我在上一篇文章中已经分析过了这里不再赘述接下来分析方法的逻辑通过获取所有的拓展类检查缓存若缓存不为空则直接返回缓存创建自适应拓展类方法也包含了三个步骤如下调用获取所有的拓展类检查缓存若缓存不为空则返回缓存若缓存为空则调用创建自适应拓展类这三个步骤看起来平淡无奇似乎没有多讲的必要但是这些平淡无奇的代码中隐藏了一些细节需要说明一下首先从第一个步骤说起这个方法用于获取某个接口的所有实现类比如该方法可以获取接口的等实现类在获取实现类的过程中如果某个某个实现类被注解修饰了那么该类就会被赋值给变量此时上面步骤中的第二步条件成立缓存不为空直接返回即可如果所有的实现类均未被注解修饰那么执行第三步逻辑创建自适应拓展类相关代码如下构建自适应拓展代码获取编译器实现类编译代码生成方法用于生成自适应拓展类该方法首先会生成自适应拓展类的源码然后通过实例默认使用作为编译器编译源码得到代理类实例接下来我将重点分析代理类代码生成逻辑至于代码编译的过程并非本文范畴这里就不分析了大家有兴趣可以自己看看下面我们把目光聚焦在方法上自适应拓展类代码生成方法代码略多约有两百行代码因此在本节中我将会对该方法的代码进行拆分分析以帮助大家更好的理解代码含义注解检测在生成代理类源码之前方法首先会通过反射检测接口方法是否包含注解对于要生成自适应拓展的接口要求该接口至少有一个方法被注解修饰若不满足此条件就会抛出运行时异常相关代码如下通过反射获取所有的方法遍历方法列表检测方法上是否有注解若所有的方法上均无注解则抛出异常生成类通过注解检测后即可开始生成代码代码生成的顺序与文件内容顺序一致首先会生成语句然后生成语句紧接着生成类名等代码整个逻辑如下生成代码所在包生成代码全限定名生成类代码简单名称全限定名生成方法这里我用占位符代表其他代码的生成逻辑该部分逻辑我将在随后进行分析上面代码不是很难理解这里我直接通过一个例子展示该段代码所生成的内容以的接口为例生成的代码如下省略方法代码生成方法一个方法可以被注解修饰也可以不被修饰这里将未被注解修饰的方法称为无注解方法下面我们先来看看此种方法的代码生成逻辑是怎样的无注解方法代码生成对于接口方法我们可以按照需求标注注解以接口为例该接口的和未标注注解其他方法均标注了注解不会为没有标注注解的方法生成代理逻辑对于该种类型的方法仅会生成一句抛出异常的代码生成逻辑如下省略无关逻辑如果方法上无注解则生成代码生成规则方法签名全限定接口名省略无关逻辑省略无关逻辑以接口的方法为例上面代码生成的内容如下获取数据前面说过方法代理逻辑会从中提取目标拓展的名称因此代码生成逻辑的一个重要的任务是从方法的参数列表获取其他参数中获取数据举个例子说明一下我们要为接口的和方法生成代理逻辑在运行时通过反射得到的方法定义大致如下对于方法通过遍历的参数列表即可获取数据这个还比较简单对于方法获取数据则要麻烦一些参数列表中没有参数因此需要从参数中获取数据获取方式是调用中可返回的方法比如如果中无相关方法此时则会抛出异常整个逻辑如下无注解方法代码生成遍历参数列表确定参数位置参数列表中存在参数为类型参数生成判空代码格式如下为类型参数生成赋值代码即或或参数列表中不存在类型参数遍历方法的参数类型列表获取某一类型参数的全部方法遍历方法列表寻找可返回的方法方法名以开头或方法名大于个字符方法的访问权限为方法非静态类型方法参数数量为方法返回值类型为结束循环如果所有参数中均不包含可返回的方法则抛出异常为包含可返回的参数生成判空代码格式如下参数全限定名为方法返回的生成判空代码格式如下方法名参数全限定名生成赋值语句格式如下全限定名方法名比如省略无关代码省略无关代码上面代码有点多但并不是很难看懂这段代码主要是为了获取数据并为之生成判空和赋值代码以的和方法为例上面代码会为它们生成如下内容代码已格式化获取注解值注解值类型为可填写多个值默认情况下为空数组若为非空数组直接获取数组内容即可若为空数组则需进行额外处理处理的过程是将类名转换为字符数组然后遍历字符数组并将字符加入到中若字符为大写字母则向中添加点号随后将字符变为小写存入中比如经过处理后得到无注解方法代码生成获取数据为空数组获取类名并将类名转换为字符数组遍历字节数组检测当前字符是否为大写字母向中添加点号将字符变为小写并添加到中添加字符到中省略无关代码省略无关逻辑检测参数此段逻辑是检测方法列表中是否存在类型的参数若存在则为其生成判空代码和其他一些代码相应的逻辑如下获取参数类型列表无注解方法代码生成获取数据获取注解值遍历参数类型列表判断当前参数名称是否等于为类型参数生成判空代码生成方法调用代码格式为设置为省略无关逻辑生成拓展名获取逻辑本段逻辑用于根据和注解值生成拓展名获取逻辑同时生成逻辑也受类型参数影响综合因素导致本段逻辑相对复杂本段逻辑可以会生成但不限于下面的代码或亦或是本段逻辑复杂指出在于条件分支比较多大家在阅读源码时需要知道每个条件分支的意义是什么否则不太容易看懂相关代码好了其他的就不多说了开始分析本段逻辑无注解方法代码生成获取数据获取注解值检测参数设置默认拓展名注解值比如接口上标注的注解值为默认情况下注解值为空串此时遍历这里的是的注解值节分析过变量的获取过程此处循环目的是生成从中获取拓展名的代码生成的代码会赋值给变量注意这个循环的遍历顺序是由后向前遍历的当为最后一个元素的坐标时默认拓展名非空是的一部分可通过方法获取其他的则是从参数中获取所以这里要判断是否为用于标识方法参数列表中是否有类型参数生成的代码功能等价于下面的代码以接口的方法为例最终生成的代码如下生成的代码功能等价于下面的代码生成的代码功能等价于下面的代码默认拓展名为空生成代码格式同上生成的代码功能等价于下面的代码生成从中获取协议的代码比如生成代码格式同上生成的代码功能等价于下面的代码以接口的方法为例最终生成的代码如下生成的代码功能等价于下面的代码以接口的方法为例最终生成的代码如下生成赋值代码生成判空代码省略无关逻辑上面代码已经进行了大量的注释不过看起来任然不是很好理解既然如此那么建议大家写点测试代码对以及等接口的自适应拓展类代码生成过程进行调试这里我以接口的自适应拓展类代码生成过程进行分析首先看一下接口的定义如下下面对方法代理逻辑生成的过程进行分析此时生成代理逻辑所用到的变量和值如下下面对数组进行遍历此时生成的代码如下接下来循环继续执行此时生成的代码如下循环结束运行现在生成变量及判空代码如下到此方法的拓展名获取代码就生成好了如果大家不是很明白建议自己调试走一遍好了本节先到这里生成拓展加载与目标方法调用逻辑上一节的逻辑生成拓展名获取逻辑接下来要做的是根据拓展名加载拓展实例并调用拓展实例的目标方法相关逻辑如下无注解方法代码生成获取数据获取注解值检测参数生成拓展名获取逻辑生成拓展获取代码格式如下全限定名全限定名全限定名全限定名格式化字符串中的表示使用前一个转换符所描述的参数即全限定名如果方法有返回值类型非则生成语句生成目标方法调用逻辑格式为方法名省略无关逻辑以接口举例说明上面代码生成的内容如下生成完整的方法本节进行代码生成的收尾工作主要用于生成方法定义的代码相关逻辑如下无注解方法代码生成获取数据获取注解值检测参数生成拓展名获取逻辑生成拓展加载与目标方法调用逻辑返回值全限定名方法名添加参数列表代码添加异常抛出代码以的方法为例上面代码生成的内容如下方法体基于动态代理实现知识与拓展我在第一章介绍自适应拓展原理时说过通过生成和编译代码实现自适应拓展的方式有点复杂不利于维护另外这样做对源码学习读者来说也不是很友好我敢肯定有同学会像我一样在开始调试源码时不知道如何调试各种自适应拓展类比如如果你也有类似的困惑这里教大家一个方法如下在方法的第一行打个断点启动测试代码代码运行到端点处单步越过断点此时可以得到生成的代码拷贝出刚刚获取到的代码到指定的包下创建同名类并将代码拷过去格式化一下即可从调试信息中可知所在包为因此接下来到包下创建类并把变量值拷贝到刚创建的文件中既然实现的机制不利于调试那么我们可以对其进行改造改造后的代码如下获取数据获取注解值获取参数获取拓展名获取拓展实例通过反射调用目标方法这样看起来是不是简单了一些不过这并不是全部的代码我将数据以及注解值的获取逻辑封装在了私有方法中相应的代码如下现在我们将放置到所在包下并对的方法代码进行改造如下以上就是改造后的代码需要特别说明的是上面的代码仅供演示使用代码逻辑并不是十分严谨如果你有更好的写法欢迎分享总结到此关于自适应拓展的原理实现以及改造过程就分析完了总的来说自适应拓展整个逻辑还是很复杂的并不是很容易弄懂因此大家在阅读该部分源码时耐心一些同时多进行调试亦或是通过生成好的代码思考生成逻辑当然大家也可以将代码生成逻辑看成一个黑盒不懂细节也没关系只要知道自适应拓展原理即可好了本篇文章先到这里感谢大家的阅读",isPost:!0,isHome:!1,isHighlightShrink:!0,isToc:!0,postUpdate:"2020-09-20 22:29:54",postMainColor:""}</script><noscript><style>#nav{opacity:1}.justified-gallery img{opacity:1}#post-meta time,#recent-posts time{display:inline!important}</style></noscript><script>(e=>{e.saveToLocal={set:(e,t,a)=>{var o;0!==a&&(o=Date.now(),localStorage.setItem(e,JSON.stringify({value:t,expiry:o+864e5*a})))},get:e=>{var t=localStorage.getItem(e);if(t){t=JSON.parse(t);if(!(Date.now()>t.expiry))return t.value;localStorage.removeItem(e)}}},e.getScript=(o,c={})=>new Promise((t,e)=>{const a=document.createElement("script");a.src=o,a.async=!0,a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},Object.keys(c).forEach(e=>{a.setAttribute(e,c[e])}),document.head.appendChild(a)}),e.getCSS=(o,c=!1)=>new Promise((t,e)=>{const a=document.createElement("link");a.rel="stylesheet",a.href=o,c&&(a.id=c),a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},document.head.appendChild(a)}),e.activateDarkMode=()=>{document.documentElement.setAttribute("data-theme","dark"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#0d0d0d")},e.activateLightMode=()=>{document.documentElement.setAttribute("data-theme","light"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#ffffff")};var e=saveToLocal.get("theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches,a=window.matchMedia("(prefers-color-scheme: light)").matches,o=window.matchMedia("(prefers-color-scheme: no-preference)").matches,c=!t&&!a&&!o,t=(void 0===e?(a?activateLightMode():t?activateDarkMode():(o||c)&&((a=(new Date).getHours())<=6||18<=a?activateDarkMode:activateLightMode)(),window.matchMedia("(prefers-color-scheme: dark)").addListener(e=>{void 0===saveToLocal.get("theme")&&(e.matches?activateDarkMode:activateLightMode)()})):("light"===e?activateLightMode:activateDarkMode)(),saveToLocal.get("aside-status"));void 0!==t&&("hide"===t?document.documentElement.classList.add("hide-aside"):document.documentElement.classList.remove("hide-aside"));/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)&&document.documentElement.classList.add("apple")})(window)</script><link rel="stylesheet" href="/css/1.min.css?1" media="async" onload='this.media="all"'><link rel="stylesheet" href="/css/bg.css?1" media="async" onload='this.media="all"'><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="云少IT" type="application/atom+xml"><link rel="alternate" href="/rss.xml" title="云少IT" type="application/rss+xml"></head><body data-type="anzhiyu"><div id="web_bg"></div><div id="an_music_bg"></div><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.10/progress_bar/progress_bar.css"><script async src="https://cdn.cbd.int/pace-js@1.2.4/pace.min.js" data-pace-options="{ &quot;restartOnRequestAfter&quot;:false,&quot;eventLag&quot;:false}"></script><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><div id="nav-group"><span id="blog_name"><div class="back-home-button"><i class="anzhiyufont anzhiyu-icon-grip-vertical"></i><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div></div><a id="site-name" href="/" accesskey="h"><div class="title">云少IT</div><i class="anzhiyufont anzhiyu-icon-house-chimney"></i></a></span><div class="mask-name-container"><div id="name-container"><a id="page-name" href="javascript:anzhiyu.scrollToDest(0, 500)" rel="external nofollow noreferrer">PAGE_NAME</a></div></div><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div></div><div id="nav-right"><div class="nav-button only-home" id="travellings_button" title="随机前往一个开往项目网站"><a class="site-page" onclick="anzhiyu.totraveling()" title="随机前往一个开往项目网站" href="javascript:void(0);" rel="external nofollow" data-pjax-state="external"><i class="anzhiyufont anzhiyu-icon-train"></i></a></div><div class="nav-button" id="randomPost_button"><a class="site-page" onclick="toRandomPost()" title="随机前往一个文章" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-dice"></i></a></div><div class="nav-button" id="search-button"><a class="site-page social-icon search" href="javascript:void(0);" rel="external nofollow noreferrer" title="搜索🔍" accesskey="s"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span> 搜索</span></a></div><input id="center-console" type="checkbox"><label class="widget" for="center-console" title="中控台" onclick="anzhiyu.switchConsole()"><i class="left"></i><i class="widget center"></i><i class="widget right"></i></label><div id="console"><div class="console-card-group-reward"><ul class="reward-all console-card"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" alt="wechat" src="/img/wxpay.webp"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" alt="alipay" src="/img/alipay.webp"></a><div class="post-qr-code-desc">alipay</div></li></ul></div><div class="console-card-group"><div class="console-card-group-left"><div class="console-card" id="card-newest-comments"><div class="card-content"><div class="author-content-item-tips">互动</div><span class="author-content-item-title">最新评论</span></div><div class="aside-list"><span>正在加载中...</span></div></div></div><div class="console-card-group-right"><div class="console-card tags"><div class="card-content"><div class="author-content-item-tips">兴趣点</div><span class="author-content-item-title">寻找你感兴趣的领域</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:1.05rem;color:#637571">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:1.05rem;color:#8a0460">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:1.05rem;color:#6b3641">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:1.05rem;color:#5e6603">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:1.05rem;color:#abb61f">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:1.05rem;color:#6dc55c">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:1.05rem;color:#8745c2">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:1.05rem;color:#804042">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:1.05rem;color:#21b4c6">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:1.05rem;color:#0a3988">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:1.05rem;color:#29446d">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:1.05rem;color:#afc378">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:1.05rem;color:#b10843">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:1.05rem;color:#9663a4">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:1.05rem;color:#b4214d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:1.05rem;color:#220f01">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:1.05rem;color:#c4106c">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:1.05rem;color:#1f5f9f">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:1.05rem;color:#c3563b">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:1.05rem;color:#1a3364">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:1.05rem;color:#1ea132">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:1.05rem;color:#a00679">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:1.05rem;color:#9e5e9b">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:1.05rem;color:#4d47bc">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:1.05rem;color:#a56245">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:1.05rem;color:#9c8435">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:1.05rem;color:#647e28">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:1.05rem;color:#a192b9">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:1.05rem;color:#22a370">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:1.05rem;color:#6b985c">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:1.05rem;color:#bc5f40">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:1.05rem;color:#167366">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:1.05rem;color:#aa928f">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:1.05rem;color:#3a740b">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:1.05rem;color:#75c343">面试<sup>11</sup></a></div></div><hr></div></div><div class="console-card history"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-box-archiv"></i><span>文章</span></div><div class="card-archives"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-archive"></i><span>归档</span><a class="card-more-btn" href="/archives/" title="查看更多"> <i class="anzhiyufont anzhiyu-icon-angle-right"></i></a></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/06/"><span class="card-archive-list-date">六月 2023</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2021/05/"><span class="card-archive-list-date">五月 2021</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/10/"><span class="card-archive-list-date">十月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">21</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/09/"><span class="card-archive-list-date">九月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">44</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/08/"><span class="card-archive-list-date">八月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">47</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/07/"><span class="card-archive-list-date">七月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">42</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/06/"><span class="card-archive-list-date">六月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">31</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/05/"><span class="card-archive-list-date">五月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">43</span><span>篇</span></div></a></li></ul></div><hr></div></div></div><div class="button-group"><div class="console-btn-item"><a class="darkmode_switchbutton" title="显示模式切换" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-moon"></i></a></div><div class="console-btn-item" id="consoleHideAside" onclick="anzhiyu.hideAsideBtn()" title="边栏显示控制"><a class="asideSwitch"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></a></div><div class="console-btn-item on" id="consoleCommentBarrage" onclick="anzhiyu.switchCommentBarrage()" title="热评开关"><a class="commentBarrage"><i class="anzhiyufont anzhiyu-icon-message"></i></a></div><div class="console-btn-item" id="consoleMusic" onclick="anzhiyu.musicToggle()" title="音乐开关"><a class="music-switch"><i class="anzhiyufont anzhiyu-icon-music"></i></a></div></div><div class="console-mask" onclick="anzhiyu.hideConsole()" href="javascript:void(0);" rel="external nofollow noreferrer"></div></div><div class="nav-button" id="nav-totop"><a class="totopbtn" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i><span id="percent" onclick="anzhiyu.scrollToDest(0,500)">0</span></a></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer" title="切换"><i class="anzhiyufont anzhiyu-icon-bars"></i></a></div></div></div></nav><div id="post-info"><div id="post-firstinfo"><div class="meta-firstline"><a class="post-meta-original">原创</a><span class="article-meta tags"></span></div></div><h1 class="post-title" itemprop="name headline">Dubbo 源码分析 – 自适应拓展原理</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="anzhiyufont anzhiyu-icon-calendar-days post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" itemprop="dateCreated datePublished" datetime="2020-09-20T14:29:54.000Z" title="发表于 2020-09-20 22:29:54">2020-09-20</time><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-history post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" itemprop="dateCreated datePublished" datetime="2020-09-20T14:29:54.000Z" title="更新于 2020-09-20 22:29:54">2020-09-20</time></span></div><div class="meta-secondline"><span class="post-meta-separator"></span><span class="post-meta-wordcount"><i class="anzhiyufont anzhiyu-icon-file-word post-meta-icon" title="文章字数"></i><span class="post-meta-label" title="文章字数">字数总计:</span><span class="word-count" title="文章字数">8.1k</span><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-clock post-meta-icon" title="阅读时长"></i><span class="post-meta-label" title="阅读时长">阅读时长:</span><span>33分钟</span></span><span class="post-meta-separator"></span><span class="post-meta-pv-cv" data-flag-title="Dubbo 源码分析 – 自适应拓展原理"><i class="anzhiyufont anzhiyu-icon-fw-eye post-meta-icon"></i><span class="post-meta-label" title="阅读量">阅读量:</span><span id="busuanzi_value_page_pv"><i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-spin"></i></span></span><span class="post-meta-separator"> </span><span class="post-meta-position" title="作者IP属地为武汉"><i class="anzhiyufont anzhiyu-icon-location-dot"></i>武汉</span></div></div></div><section class="main-hero-waves-area waves-area"><svg class="waves-svg" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M -160 44 c 30 0 58 -18 88 -18 s 58 18 88 18 s 58 -18 88 -18 s 58 18 88 18 v 44 h -352 Z"></path></defs><g class="parallax"><use href="#gentle-wave" x="48" y="0"></use><use href="#gentle-wave" x="48" y="3"></use><use href="#gentle-wave" x="48" y="5"></use><use href="#gentle-wave" x="48" y="7"></use></g></svg></section><div id="post-top-cover"><img class="nolazyload" id="post-top-bg" src="https://www.bing.com/th?id=OHR.SeaAngel_EN-IN2839358434_UHD.jpg"></div></header><main id="blog-container"><div class="layout" id="content-inner"><div id="post"><div class="post-ai-description"><div class="ai-title"><i class="anzhiyufont anzhiyu-icon-bilibili"></i><div class="ai-title-text">AI-摘要</div><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i><i class="anzhiyufont anzhiyu-icon-circle-dot" title="朗读摘要"></i><div id="ai-tag">Tianli GPT</div></div><div class="ai-explanation">AI初始化中...</div><div class="ai-btn-box"><div class="ai-btn-item">介绍自己 🙈</div><div class="ai-btn-item">生成本文简介 👋</div><div class="ai-btn-item">推荐相关文章 📖</div><div class="ai-btn-item">前往主页 🏠</div><div class="ai-btn-item" id="go-tianli-blog">前往爱发电购买</div></div><script data-pjax src="/js/anzhiyu/ai_abstract.js"></script></div><article class="post-content" id="article-container" itemscope itemtype="https://it985.github.io/posts/cf451fe.html"><header><h1 id="CrawlerTitle" itemprop="name headline">Dubbo 源码分析 – 自适应拓展原理</h1><span itemprop="author" itemscope itemtype="http://schema.org/Person">云少</span><time itemprop="dateCreated datePublished" datetime="2020-09-20T14:29:54.000Z" title="发表于 2020-09-20 22:29:54">2020-09-20</time><time itemprop="dateCreated datePublished" datetime="2020-09-20T14:29:54.000Z" title="更新于 2020-09-20 22:29:54">2020-09-20</time></header><h1 id="二、Dubbo-源码分析-–-自适应拓展原理"><a href="#二、Dubbo-源码分析-–-自适应拓展原理" class="headerlink" title="二、Dubbo 源码分析 – 自适应拓展原理"></a>二、Dubbo 源码分析 – 自适应拓展原理</h1><h2 id="1-原理"><a href="#1-原理" class="headerlink" title="1.原理"></a>1.原理</h2><p>我在上一篇文章中分析了 Dubbo 的 SPI 机制，Dubbo SPI 是 Dubbo 框架的核心。Dubbo 中的很多拓展都是通过 SPI 机制进行加载的，比如 Protocol、Cluster、LoadBalance 等。有时，有些拓展并非想在框架启动阶段被加载，而是希望在拓展方法被调用时，根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载，那么拓展方法就无法被调用（静态方法除外）。拓展方法未被调用，就无法进行加载，这似乎是个死结。不过好在也有相应的解决办法，通过代理模式就可以解决这个问题，这里我们将具有代理功能的拓展称之为自适应拓展。Dubbo 并未直接通过代理模式实现自适应拓展，而是代理代理模式基础上，封装了一个更炫的实现方式。Dubbo 首先会为拓展接口生成具有代理功能的代码，然后通过 javassist 或 jdk 编译这段代码，得到 Class 类，最后在通过反射创建代理类。整个过程比较复杂、炫丽。如此复杂的过程最终的目的是为拓展生成代理对象，但实际上每个代理对象的代理逻辑基本一致，均是从 URL 中获取欲加载实现类的名称。因此，我们完全可以把代理逻辑抽出来，并通过动态代理的方式实现自适应拓展。这样做的好处显而易见，方便维护，也方便源码学习者学习和调试代码。本文将在随后实现一个动态代理版的自适应拓展，有兴趣的同学可以继续往下读。</p><p>接下来，我们通过一个示例演示自适应拓展类。这个示例取自 Dubbo 官方文档，我这里进行了一定的拓展。这是一个与汽车相关的例子，我们有一个车轮制造厂接口 WheelMaker：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">WheelMaker</span> &#123;</span><br><span class="line">    Wheel <span class="title function_">makeWheel</span><span class="params">(URL url)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>WheelMaker 接口的 Adaptive 实现类如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdaptiveWheelMaker</span> <span class="keyword">implements</span> <span class="title class_">WheelMaker</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> Wheel <span class="title function_">makeWheel</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 1.从 URL 中获取 WheelMaker 名称</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">wheelMakerName</span> <span class="operator">=</span> url.getParameter(<span class="string">&quot;Wheel.maker&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (name == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;wheelMakerName == null&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 2.通过 SPI 加载具体的 WheelMaker</span></span><br><span class="line">        <span class="type">WheelMaker</span> <span class="variable">wheelMaker</span> <span class="operator">=</span> ExtensionLoader</span><br><span class="line">            .getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 3.调用目标方法</span></span><br><span class="line">        <span class="keyword">return</span> wheelMaker.makeWheel(URL url);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>AdaptiveWheelMaker 是一个代理类，它主要做了三件事情：</p><ol><li>从 URL 中获取 WheelMaker 名称</li><li>通过 SPI 加载具体的 WheelMaker</li><li>调用目标方法</li></ol><p>接下来，我们来看看汽车制造厂 CarMaker 接口与其实现类。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">CarMaker</span> &#123;</span><br><span class="line">    Car <span class="title function_">makeCar</span><span class="params">(URL url)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RaceCarMaker</span> <span class="keyword">implements</span> <span class="title class_">CarMaker</span> &#123;</span><br><span class="line">    WheelMaker wheelMaker;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过 setter 注入 AdaptiveWheelMaker</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">setWheelMaker</span><span class="params">(WheelMaker wheelMaker)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.wheelMaker = wheelMaker;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Car <span class="title function_">makeCar</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">        <span class="type">Wheel</span> <span class="variable">wheel</span> <span class="operator">=</span> wheelMaker.makeWheel(url);</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">RaceCar</span>(wheel, ...);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>RaceCarMaker 持有一个 WheelMaker 类型从成员变量，在程序启动时，我们可以将 AdaptiveWheelMaker 通过 setter 方法注入到 RaceCarMaker 中。在运行时，假设有这样一个 URL 类型的参数：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker</span><br></pre></td></tr></table></figure><p>RaceCarMaker 的 makeCar 方法将上面的 url 作为参数传给 AdaptiveWheelMaker 的 makeWheel 方法，makeWheel 方法从 url 中提取 wheel.maker 参数，得到 MichelinWheelMaker。之后再通过 SPI 加载名为 MichelinWheelMaker 的实现类，得到具体的 WheelMaker 实例。</p><p>上面这个示例展示了自适应拓展类的核心实现 – 在组件方法被调用时，通过代理的方式加载指定的实现类，并调用被代理的方法。</p><p>经过以上说明，大家应该搞懂了自适应拓展的原理。接下来，我们深入到源码中，探索自适应拓展生成的过程。</p><h2 id="2-源码分析"><a href="#2-源码分析" class="headerlink" title="2.源码分析"></a>2.源码分析</h2><p>在对自适应拓展生成过程进行深入分析之前，我们先来看一下与自适应拓展息息相关的一个注解，即 Adaptive 注解。该注解的定义如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Target(&#123;ElementType.TYPE, ElementType.METHOD&#125;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Adaptive &#123;</span><br><span class="line">    String[] value() <span class="keyword">default</span> &#123;&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从上面的代码中可知，Adaptive 可注解在类或方法上。注解在类上时，Dubbo 不会为该类生成代理类。注解上方法（接口方法）上时，Dubbo 会为为该方法生成代理逻辑。Adaptive 注解在类上的情况很少，在 Dubbo 中，仅有两个类被 Adaptive 注解了，分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况表示拓展的加载逻辑由人工编码完成。更多时候，Adaptive 是注解在接口方法上的，表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同，相应的处理逻辑也是不同的。注解在类上时，处理逻辑比较简单，本文就不分析了。注解在接口方法上时，处理逻辑较为复杂，本章将会重点分析此块逻辑。接下来，我们从 getAdaptiveExtension 方法进行分析。代码如下：</p><h3 id="2-1-获取自适应拓展"><a href="#2-1-获取自适应拓展" class="headerlink" title="2.1 获取自适应拓展"></a>2.1 获取自适应拓展</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> T <span class="title function_">getAdaptiveExtension</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 从缓存中获取自适应拓展</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">instance</span> <span class="operator">=</span> cachedAdaptiveInstance.get();</span><br><span class="line">    <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;    <span class="comment">// 缓存未命中</span></span><br><span class="line">        <span class="keyword">if</span> (createAdaptiveInstanceError == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (cachedAdaptiveInstance) &#123;</span><br><span class="line">                instance = cachedAdaptiveInstance.get();</span><br><span class="line">                <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        <span class="comment">// 创建自适应拓展</span></span><br><span class="line">                        instance = createAdaptiveExtension();</span><br><span class="line">                        <span class="comment">// 设置拓展到缓存中</span></span><br><span class="line">                        cachedAdaptiveInstance.set(instance);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">                        createAdaptiveInstanceError = t;</span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;...&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;...&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (T) instance;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>getAdaptiveExtension 方法首先会检查缓存，缓存未命中，则调用 createAdaptiveExtension 方法创建自适应拓展。下面，我们看一下 createAdaptiveExtension 方法的代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> T <span class="title function_">createAdaptiveExtension</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 获取自适应拓展类，并通过反射实例化</span></span><br><span class="line">        <span class="keyword">return</span> injectExtension((T) getAdaptiveExtensionClass().newInstance());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>createAdaptiveExtension 方法代码比较少，但却包含了三个动作，分别如下：</p><ol><li>调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象</li><li>通过反射进行实例化</li><li>调用 injectExtension 方法向拓展实例中注入依赖</li></ol><p>前两个动作比较好理解，第三个动作不好理解，这里简单说明一下。injectExtension 方法通过 setter 方法向目标对象中注入依赖，可以看做是一个简单 IOC 的实现。前面说过，Dubbo 中有两种类型的自适应拓展，一种是手工编码的，一种是自动生成的。手工编码的 Adaptive 拓展中可能存在着一些依赖，而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖，这一点需要大家注意一下。关于 injectExtension 方法，我在上一篇文章中已经分析过了，这里不再赘述。接下来，分析 getAdaptiveExtensionClass 方法的逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Class&lt;?&gt; getAdaptiveExtensionClass() &#123;</span><br><span class="line">    <span class="comment">// 通过 SPI 获取所有的拓展类</span></span><br><span class="line">    getExtensionClasses();</span><br><span class="line">    <span class="comment">// 检查缓存，若缓存不为空，则直接返回缓存</span></span><br><span class="line">    <span class="keyword">if</span> (cachedAdaptiveClass != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> cachedAdaptiveClass;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 创建自适应拓展类</span></span><br><span class="line">    <span class="type">return</span> <span class="variable">cachedAdaptiveClass</span> <span class="operator">=</span> createAdaptiveExtensionClass();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>getAdaptiveExtensionClass 方法也包含了三个步骤，如下：</p><ol><li>调用 getExtensionClasses 获取所有的拓展类</li><li>检查缓存，若缓存不为空，则返回缓存</li><li>若缓存为空，则调用 createAdaptiveExtensionClass 创建自适应拓展类</li></ol><p>这三个步骤看起来平淡无奇，似乎没有多讲的必要。但是这些平淡无奇的代码中隐藏了一些细节，需要说明一下。首先从第一个步骤说起，getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中，如果某个某个实现类被 Adaptive 注解修饰了，那么该类就会被赋值给 cachedAdaptiveClass 变量。此时，上面步骤中的第二步条件成立（缓存不为空），直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰，那么执行第三步逻辑，创建自适应拓展类。相关代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Class&lt;?&gt; createAdaptiveExtensionClass() &#123;</span><br><span class="line">    <span class="comment">// 构建自适应拓展代码</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">code</span> <span class="operator">=</span> createAdaptiveExtensionClassCode();</span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> findClassLoader();</span><br><span class="line">    <span class="comment">// 获取编译器实现类</span></span><br><span class="line">    com.alibaba.dubbo.common.compiler.<span class="type">Compiler</span> <span class="variable">compiler</span> <span class="operator">=</span> ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();</span><br><span class="line">    <span class="comment">// 编译代码，生成 Class</span></span><br><span class="line">    <span class="keyword">return</span> compiler.compile(code, classLoader);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>createAdaptiveExtensionClass 方法用于生成自适应拓展类，该方法首先会生成自适应拓展类的源码，然后通过 Compiler 实例（Dubbo 默认使用 javassist 作为编译器）编译源码，得到代理类 Class 实例。接下来，我将重点分析代理类代码生成逻辑。至于代码编译的过程，并非本文范畴，这里就不分析了，大家有兴趣可以自己看看。下面，我们把目光聚焦在 createAdaptiveExtensionClassCode 方法上。</p><h3 id="2-2-自适应拓展类代码生成"><a href="#2-2-自适应拓展类代码生成" class="headerlink" title="2.2 自适应拓展类代码生成"></a>2.2 自适应拓展类代码生成</h3><p>createAdaptiveExtensionClassCode 方法代码略多，约有两百行代码。因此在本节中，我将会对该方法的代码进行拆分分析，以帮助大家更好的理解代码含义。</p><h4 id="2-2-1-Adaptive-注解检测"><a href="#2-2-1-Adaptive-注解检测" class="headerlink" title="2.2.1 Adaptive 注解检测"></a>2.2.1 Adaptive 注解检测</h4><p>在生成代理类源码之前，createAdaptiveExtensionClassCode 方法首先会通过反射检测接口方法是否包含 Adaptive 注解。对于要生成自适应拓展的接口，Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件，就会抛出运行时异常。相关代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过反射获取所有的方法</span></span><br><span class="line">Method[] methods = type.getMethods();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">hasAdaptiveAnnotation</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="comment">// 遍历方法列表</span></span><br><span class="line"><span class="keyword">for</span> (Method m : methods) &#123;</span><br><span class="line">    <span class="comment">// 检测方法上是否有 Adaptive 注解</span></span><br><span class="line">    <span class="keyword">if</span> (m.isAnnotationPresent(Adaptive.class)) &#123;</span><br><span class="line">        hasAdaptiveAnnotation = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!hasAdaptiveAnnotation)</span><br><span class="line">    <span class="comment">// 若所有的方法上均无 Adaptive 注解，则抛出异常</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;...&quot;</span>);</span><br></pre></td></tr></table></figure><h4 id="2-2-2-生成类"><a href="#2-2-2-生成类" class="headerlink" title="2.2.2 生成类"></a>2.2.2 生成类</h4><p>通过 Adaptive 注解检测后，即可开始生成代码。代码生成的顺序与 Java 文件内容顺序一致，首先会生成 package 语句，然后生成 import 语句，紧接着生成类名等代码。整个逻辑如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 生成 package 代码：package + type 所在包</span></span><br><span class="line">codeBuilder.append(<span class="string">&quot;package &quot;</span>).append(type.getPackage().getName()).append(<span class="string">&quot;;&quot;</span>);</span><br><span class="line"><span class="comment">// 生成 import 代码：import + ExtensionLoader 全限定名</span></span><br><span class="line">codeBuilder.append(<span class="string">&quot;\nimport &quot;</span>).append(ExtensionLoader.class.getName()).append(<span class="string">&quot;;&quot;</span>);</span><br><span class="line"><span class="comment">// 生成类代码：public class + type简单名称 + $Adaptive + implements + type全限定名 + &#123;</span></span><br><span class="line">codeBuilder.append(<span class="string">&quot;\npublic class &quot;</span>)</span><br><span class="line">    .append(type.getSimpleName())</span><br><span class="line">    .append(<span class="string">&quot;$Adaptive&quot;</span>)</span><br><span class="line">    .append(<span class="string">&quot; implements &quot;</span>)</span><br><span class="line">    .append(type.getCanonicalName())</span><br><span class="line">    .append(<span class="string">&quot; &#123;&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// $&#123;生成方法&#125;</span></span><br><span class="line"></span><br><span class="line">codeBuilder.append(<span class="string">&quot;\n&#125;&quot;</span>);</span><br></pre></td></tr></table></figure><p>这里，我用 ${…} 占位符代表其他代码的生成逻辑，该部分逻辑我将在随后进行分析。上面代码不是很难理解，这里我直接通过一个例子展示该段代码所生成的内容。以 Dubbo 的 Protocol 接口为例，生成的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.alibaba.dubbo.rpc;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.dubbo.common.extension.ExtensionLoader;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Protocol$Adaptive</span> <span class="keyword">implements</span> <span class="title class_">com</span>.alibaba.dubbo.rpc.Protocol &#123;</span><br><span class="line">    <span class="comment">// 省略方法代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2-2-3-生成方法"><a href="#2-2-3-生成方法" class="headerlink" title="2.2.3 生成方法"></a>2.2.3 生成方法</h4><p>一个方法可以被 Adaptive 注解修饰，也可以不被修饰。这里将未被 Adaptive 注解修饰的方法称为“无 Adaptive 注解方法”，下面我们先来看看此种方法的代码生成逻辑是怎样的。</p><h5 id="2-2-3-1-无-Adaptive-注解方法代码生成"><a href="#2-2-3-1-无-Adaptive-注解方法代码生成" class="headerlink" title="2.2.3.1 无 Adaptive 注解方法代码生成"></a>2.2.3.1 无 Adaptive 注解方法代码生成</h5><p>对于接口方法，我们可以按照需求标注 Adaptive 注解。以 Protocol 接口为例，该接口的 destroy 和 getDefaultPort 未标注 Adaptive 注解，其他方法均标注了 Adaptive 注解。Dubbo 不会为没有标注 Adaptive 注解的方法生成代理逻辑，对于该种类型的方法，仅会生成一句抛出异常的代码。生成逻辑如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关逻辑</span></span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="comment">// 如果方法上无 Adaptive 注解，则生成 throw new UnsupportedOperationException(...) 代码</span></span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 生成规则：</span></span><br><span class="line">        <span class="comment">// throw new UnsupportedOperationException(</span></span><br><span class="line">        <span class="comment">//     &quot;method &quot; + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”)</span></span><br><span class="line">        code.append(<span class="string">&quot;throw new UnsupportedOperationException(\&quot;method &quot;</span>)</span><br><span class="line">            .append(method.toString()).append(<span class="string">&quot; of interface &quot;</span>)</span><br><span class="line">            .append(type.getName()).append(<span class="string">&quot; is not adaptive method!\&quot;);&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 省略无关逻辑</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以 Protocol 接口的 destroy 方法为例，上面代码生成的内容如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>(</span><br><span class="line">            <span class="string">&quot;method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!&quot;</span>);</span><br></pre></td></tr></table></figure><h5 id="2-2-3-2-获取-URL-数据"><a href="#2-2-3-2-获取-URL-数据" class="headerlink" title="2.2.3.2 获取 URL 数据"></a>2.2.3.2 获取 URL 数据</h5><p>前面说过方法代理逻辑会从 URL 中提取目标拓展的名称，因此代码生成逻辑的一个重要的任务是从方法的参数列表获取其他参数中获取 URL 数据。举个例子说明一下，我们要为 Protocol 接口的 refer 和 export 方法生成代理逻辑。在运行时，通过反射得到的方法定义大致如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Invoker <span class="title function_">refer</span><span class="params">(Class&lt;T&gt; arg0, URL arg1)</span> <span class="keyword">throws</span> RpcException;</span><br><span class="line">Exporter <span class="title function_">export</span><span class="params">(Invoker&lt;T&gt; arg0)</span> <span class="keyword">throws</span> RpcException;</span><br></pre></td></tr></table></figure><p>对于 refer 方法，通过遍历 refer 的参数列表即可获取 URL 数据，这个还比较简单。对于 export 方法，获取 URL 数据则要麻烦一些。export 参数列表中没有 URL 参数，因此需要从 Invoker 参数中获取 URL 数据。获取方式是调用 Invoker 中可返回 URL 的 getter 方法，比如 getUrl。如果 Invoker 中无相关 getter 方法，此时则会抛出异常。整个逻辑如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">    Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    Class&lt;?&gt;[] ets = method.getExceptionTypes();</span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// $&#123;无 Adaptive 注解方法代码生成&#125;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">urlTypeIndex</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="comment">// 遍历参数列表，确定 URL 参数位置</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (pts[i].equals(URL.class)) &#123;</span><br><span class="line">                urlTypeIndex = i;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (urlTypeIndex != -<span class="number">1</span>) &#123;    <span class="comment">// 参数列表中存在 URL 参数</span></span><br><span class="line">            <span class="comment">// 为 URL 类型参数生成判空代码，格式如下：</span></span><br><span class="line">            <span class="comment">// if (arg + urlTypeIndex == null) </span></span><br><span class="line">            <span class="comment">//     throw new IllegalArgumentException(&quot;url == null&quot;);</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> String.format(<span class="string">&quot;\nif (arg%d == null) throw new IllegalArgumentException(\&quot;url == null\&quot;);&quot;</span>,</span><br><span class="line">                                     urlTypeIndex);</span><br><span class="line">            code.append(s);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 为 URL 类型参数生成赋值代码，即 URL url = arg1 或 arg2，或 argN</span></span><br><span class="line">            s = String.format(<span class="string">&quot;\n%s url = arg%d;&quot;</span>, URL.class.getName(), urlTypeIndex);</span><br><span class="line">            code.append(s);</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;    <span class="comment">// 参数列表中不存在 URL 类型参数</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">attribMethod</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">            LBL_PTS:</span><br><span class="line">            <span class="comment">// 遍历方法的参数类型列表</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; ++i) &#123;</span><br><span class="line">                <span class="comment">// 获取某一类型参数的全部方法</span></span><br><span class="line">                Method[] ms = pts[i].getMethods();</span><br><span class="line">                <span class="comment">// 遍历方法列表，寻找可返回 URL 的 getter 方法</span></span><br><span class="line">                <span class="keyword">for</span> (Method m : ms) &#123;</span><br><span class="line">                    <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> m.getName();</span><br><span class="line">                    <span class="comment">// 1. 方法名以 get 开头，或方法名大于3个字符</span></span><br><span class="line">                    <span class="comment">// 2. 方法的访问权限为 public</span></span><br><span class="line">                    <span class="comment">// 3. 方法非静态类型</span></span><br><span class="line">                    <span class="comment">// 4. 方法参数数量为0</span></span><br><span class="line">                    <span class="comment">// 5. 方法返回值类型为 URL</span></span><br><span class="line">                    <span class="keyword">if</span> ((name.startsWith(<span class="string">&quot;get&quot;</span>) || name.length() &gt; <span class="number">3</span>)</span><br><span class="line">                        &amp;&amp; Modifier.isPublic(m.getModifiers())</span><br><span class="line">                        &amp;&amp; !Modifier.isStatic(m.getModifiers())</span><br><span class="line">                        &amp;&amp; m.getParameterTypes().length == <span class="number">0</span></span><br><span class="line">                        &amp;&amp; m.getReturnType() == URL.class) &#123;</span><br><span class="line">                        urlTypeIndex = i;</span><br><span class="line">                        attribMethod = name;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">// 结束 for (int i = 0; i &lt; pts.length; ++i) 循环</span></span><br><span class="line">                        <span class="keyword">break</span> LBL_PTS;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (attribMethod == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果所有参数中均不包含可返回 URL 的 getter 方法，则抛出异常</span></span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;...&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 为包含可返回 URL 的参数生成判空代码，格式如下：</span></span><br><span class="line">            <span class="comment">// if (arg + urlTypeIndex == null) </span></span><br><span class="line">            <span class="comment">//     throw new IllegalArgumentException(&quot;参数全限定名 + argument == null&quot;);</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> String.format(<span class="string">&quot;\nif (arg%d == null) throw new IllegalArgumentException(\&quot;%s argument == null\&quot;);&quot;</span>,</span><br><span class="line">                                     urlTypeIndex, pts[urlTypeIndex].getName());</span><br><span class="line">            code.append(s);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 为 getter 方法返回的 URL 生成判空代码，格式如下：</span></span><br><span class="line">            <span class="comment">// if (argN.getter方法名() == null) </span></span><br><span class="line">            <span class="comment">//     throw new IllegalArgumentException(参数全限定名 + argument getUrl() == null);</span></span><br><span class="line">            s = String.format(<span class="string">&quot;\nif (arg%d.%s() == null) throw new IllegalArgumentException(\&quot;%s argument %s() == null\&quot;);&quot;</span>,</span><br><span class="line">                              urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);</span><br><span class="line">            code.append(s);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 生成赋值语句，格式如下：</span></span><br><span class="line">            <span class="comment">// URL全限定名 url = argN.getter方法名()，比如 </span></span><br><span class="line">            <span class="comment">// com.alibaba.dubbo.common.URL url = invoker.getUrl();</span></span><br><span class="line">            s = String.format(<span class="string">&quot;%s url = arg%d.%s();&quot;</span>, URL.class.getName(), urlTypeIndex, attribMethod);</span><br><span class="line">            code.append(s);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 省略无关代码</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码有点多，但并不是很难看懂。这段代码主要是为了获取 URL 数据，并为之生成判空和赋值代码。以 Protocol 的 refer 和 export 方法为例，上面代码会为它们生成如下内容（代码已格式化）：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">refer:</span><br><span class="line"><span class="keyword">if</span> (arg1 == <span class="literal">null</span>) </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">com.alibaba.dubbo.common.<span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> arg1;</span><br><span class="line"></span><br><span class="line">export:</span><br><span class="line"><span class="keyword">if</span> (arg0 == <span class="literal">null</span>) </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;com.alibaba.dubbo.rpc.Invoker argument == null&quot;</span>);</span><br><span class="line"><span class="keyword">if</span> (arg0.getUrl() == <span class="literal">null</span>) </span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;com.alibaba.dubbo.rpc.Invoker argument getUrl() == null&quot;</span>);</span><br><span class="line">com.alibaba.dubbo.common.<span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> arg0.getUrl();</span><br></pre></td></tr></table></figure><h5 id="2-2-3-3-获取-Adaptive-注解值"><a href="#2-2-3-3-获取-Adaptive-注解值" class="headerlink" title="2.2.3.3 获取 Adaptive 注解值"></a>2.2.3.3 获取 Adaptive 注解值</h5><p>Adaptive 注解值 value 类型为 String[]，可填写多个值，默认情况下为空数组。若 value 为非空数组，直接获取数组内容即可。若 value 为空数组，则需进行额外处理。处理的过程是将类名转换为字符数组，然后遍历字符数组，并将字符加入到 StringBuilder 中。若字符为大写字母，则向 StringBuilder 中添加点号，随后将字符变为小写存入 StringBuilder 中。比如 LoadBalance 经过处理后，得到 load.balance。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">    Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    Class&lt;?&gt;[] ets = method.getExceptionTypes();</span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// $&#123;无 Adaptive 注解方法代码生成&#125;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// $&#123;获取 URL 数据&#125;</span></span><br><span class="line"></span><br><span class="line">        String[] value = adaptiveAnnotation.value();</span><br><span class="line">        <span class="comment">// value 为空数组</span></span><br><span class="line">        <span class="keyword">if</span> (value.length == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 获取类名，并将类名转换为字符数组</span></span><br><span class="line">            <span class="type">char</span>[] charArray = type.getSimpleName().toCharArray();</span><br><span class="line">            <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">128</span>);</span><br><span class="line">            <span class="comment">// 遍历字节数组</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; charArray.length; i++) &#123;</span><br><span class="line">                <span class="comment">// 检测当前字符是否为大写字母</span></span><br><span class="line">                <span class="keyword">if</span> (Character.isUpperCase(charArray[i])) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (i != <span class="number">0</span>) &#123;</span><br><span class="line">                        <span class="comment">// 向 sb 中添加点号</span></span><br><span class="line">                        sb.append(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 将字符变为小写，并添加到 sb 中</span></span><br><span class="line">                    sb.append(Character.toLowerCase(charArray[i]));</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// 添加字符到 sb 中</span></span><br><span class="line">                    sb.append(charArray[i]);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            value = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;sb.toString()&#125;;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 省略无关代码</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="2-2-3-4-检测-Invocation-参数"><a href="#2-2-3-4-检测-Invocation-参数" class="headerlink" title="2.2.3.4 检测 Invocation 参数"></a>2.2.3.4 检测 Invocation 参数</h5><p>此段逻辑是检测方法列表中是否存在 Invocation 类型的参数，若存在，则为其生成判空代码和其他一些代码。相应的逻辑如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">    Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();    <span class="comment">// 获取参数类型列表</span></span><br><span class="line">    Class&lt;?&gt;[] ets = method.getExceptionTypes();</span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// $&#123;无 Adaptive 注解方法代码生成&#125;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// $&#123;获取 URL 数据&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;获取 Adaptive 注解值&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">hasInvocation</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">// 遍历参数类型列表</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; ++i) &#123;</span><br><span class="line">            <span class="comment">// 判断当前参数名称是否等于 com.alibaba.dubbo.rpc.Invocation</span></span><br><span class="line">            <span class="keyword">if</span> (pts[i].getName().equals(<span class="string">&quot;com.alibaba.dubbo.rpc.Invocation&quot;</span>)) &#123;</span><br><span class="line">                <span class="comment">// 为 Invocation 类型参数生成判空代码</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> String.format(<span class="string">&quot;\nif (arg%d == null) throw new IllegalArgumentException(\&quot;invocation == null\&quot;);&quot;</span>, i);</span><br><span class="line">                code.append(s);</span><br><span class="line">                <span class="comment">// 生成 getMethodName 方法调用代码，格式为：</span></span><br><span class="line">                <span class="comment">//    String methodName = argN.getMethodName();</span></span><br><span class="line">                s = String.format(<span class="string">&quot;\nString methodName = arg%d.getMethodName();&quot;</span>, i);</span><br><span class="line">                code.append(s);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 设置 hasInvocation 为 true</span></span><br><span class="line">                hasInvocation = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="2-2-3-5-生成拓展名获取逻辑"><a href="#2-2-3-5-生成拓展名获取逻辑" class="headerlink" title="2.2.3.5 生成拓展名获取逻辑"></a>2.2.3.5 生成拓展名获取逻辑</h5><p>本段逻辑用于根据 SPI 和 Adaptive 注解值生成“拓展名获取逻辑”，同时生成逻辑也受 Invocation 类型参数影响，综合因素导致本段逻辑相对复杂。本段逻辑可以会生成但不限于下面的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">extName</span> <span class="operator">=</span> (url.getProtocol() == <span class="literal">null</span> ? <span class="string">&quot;dubbo&quot;</span> : url.getProtocol());</span><br></pre></td></tr></table></figure><p>或</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">extName</span> <span class="operator">=</span> url.getMethodParameter(methodName, <span class="string">&quot;loadbalance&quot;</span>, <span class="string">&quot;random&quot;</span>);</span><br></pre></td></tr></table></figure><p>亦或是</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">extName</span> <span class="operator">=</span> url.getParameter(<span class="string">&quot;client&quot;</span>, url.getParameter(<span class="string">&quot;transporter&quot;</span>, <span class="string">&quot;netty&quot;</span>));</span><br></pre></td></tr></table></figure><p>本段逻辑复杂指出在于条件分支比较多，大家在阅读源码时需要知道每个条件分支的意义是什么，否则不太容易看懂相关代码。好了，其他的就不多说了，开始分析本段逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">    Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    Class&lt;?&gt;[] ets = method.getExceptionTypes();</span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// $无 Adaptive 注解方法代码生成&#125;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// $&#123;获取 URL 数据&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;获取 Adaptive 注解值&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;检测 Invocation 参数&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 设置默认拓展名，cachedDefaultName = SPI 注解值，比如 Protocol 接口上标注的 </span></span><br><span class="line">        <span class="comment">// SPI 注解值为 dubbo。默认情况下，SPI 注解值为空串，此时 cachedDefaultName = null</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">defaultExtName</span> <span class="operator">=</span> cachedDefaultName;</span><br><span class="line">        <span class="type">String</span> <span class="variable">getNameCode</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 遍历 value，这里的 value 是 Adaptive 的注解值，2.2.3.3 节分析过 value 变量的获取过程。</span></span><br><span class="line">        <span class="comment">// 此处循环目的是生成从 URL 中获取拓展名的代码，生成的代码会赋值给 getNameCode 变量。注意这</span></span><br><span class="line">        <span class="comment">// 个循环的遍历顺序是由后向前遍历的。</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> value.length - <span class="number">1</span>; i &gt;= <span class="number">0</span>; --i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i == value.length - <span class="number">1</span>) &#123;    <span class="comment">// 当 i 为最后一个元素的坐标时</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="literal">null</span> != defaultExtName) &#123;   <span class="comment">// 默认拓展名非空</span></span><br><span class="line">                    <span class="comment">// protocol 是 url 的一部分，可通过 getProtocol 方法获取，其他的则是从</span></span><br><span class="line">                    <span class="comment">// URL 参数中获取。所以这里要判断 value[i] 是否为 protocol</span></span><br><span class="line">                    <span class="keyword">if</span> (!<span class="string">&quot;protocol&quot;</span>.equals(value[i]))</span><br><span class="line">                        <span class="comment">// hasInvocation 用于标识方法参数列表中是否有 Invocation 类型参数</span></span><br><span class="line">                        <span class="keyword">if</span> (hasInvocation)</span><br><span class="line">                            <span class="comment">// 生成的代码功能等价于下面的代码：</span></span><br><span class="line">                            <span class="comment">//   url.getMethodParameter(methodName, value[i], defaultExtName)</span></span><br><span class="line">                            <span class="comment">// 以 LoadBalance 接口的 select 方法为例，最终生成的代码如下：</span></span><br><span class="line">                            <span class="comment">//   url.getMethodParameter(methodName, &quot;loadbalance&quot;, &quot;random&quot;)</span></span><br><span class="line">                            getNameCode = String.format(<span class="string">&quot;url.getMethodParameter(methodName, \&quot;%s\&quot;, \&quot;%s\&quot;)&quot;</span>, value[i], defaultExtName);</span><br><span class="line">                        <span class="keyword">else</span></span><br><span class="line">                            <span class="comment">// 生成的代码功能等价于下面的代码：</span></span><br><span class="line">                            <span class="comment">//   url.getParameter(value[i], defaultExtName)</span></span><br><span class="line">                            getNameCode = String.format(<span class="string">&quot;url.getParameter(\&quot;%s\&quot;, \&quot;%s\&quot;)&quot;</span>, value[i], defaultExtName);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        <span class="comment">// 生成的代码功能等价于下面的代码：</span></span><br><span class="line">                        <span class="comment">//   ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )</span></span><br><span class="line">                        getNameCode = String.format(<span class="string">&quot;( url.getProtocol() == null ? \&quot;%s\&quot; : url.getProtocol() )&quot;</span>, defaultExtName);</span><br><span class="line"></span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;    <span class="comment">// 默认拓展名为空</span></span><br><span class="line">                    <span class="keyword">if</span> (!<span class="string">&quot;protocol&quot;</span>.equals(value[i]))</span><br><span class="line">                        <span class="keyword">if</span> (hasInvocation)</span><br><span class="line">                            <span class="comment">// 生成代码格式同上</span></span><br><span class="line">                            getNameCode = String.format(<span class="string">&quot;url.getMethodParameter(methodName, \&quot;%s\&quot;, \&quot;%s\&quot;)&quot;</span>, value[i], defaultExtName);</span><br><span class="line">                        <span class="keyword">else</span></span><br><span class="line">                            <span class="comment">// 生成的代码功能等价于下面的代码：</span></span><br><span class="line">                            <span class="comment">//   url.getParameter(value[i])</span></span><br><span class="line">                            getNameCode = String.format(<span class="string">&quot;url.getParameter(\&quot;%s\&quot;)&quot;</span>, value[i]);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        <span class="comment">// 生成从 url 中获取协议的代码，比如 &quot;dubbo&quot;</span></span><br><span class="line">                        getNameCode = <span class="string">&quot;url.getProtocol()&quot;</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (!<span class="string">&quot;protocol&quot;</span>.equals(value[i]))</span><br><span class="line">                    <span class="keyword">if</span> (hasInvocation)</span><br><span class="line">                        <span class="comment">// 生成代码格式同上</span></span><br><span class="line">                        getNameCode = String.format(<span class="string">&quot;url.getMethodParameter(methodName, \&quot;%s\&quot;, \&quot;%s\&quot;)&quot;</span>, value[i], defaultExtName);</span><br><span class="line">                    <span class="keyword">else</span></span><br><span class="line">                        <span class="comment">// 生成的代码功能等价于下面的代码：</span></span><br><span class="line">                        <span class="comment">//   url.getParameter(value[i], getNameCode)</span></span><br><span class="line">                        <span class="comment">// 以 Transporter 接口的 connect 方法为例，最终生成的代码如下：</span></span><br><span class="line">                        <span class="comment">//   url.getParameter(&quot;client&quot;, url.getParameter(&quot;transporter&quot;, &quot;netty&quot;))</span></span><br><span class="line">                        getNameCode = String.format(<span class="string">&quot;url.getParameter(\&quot;%s\&quot;, %s)&quot;</span>, value[i], getNameCode);</span><br><span class="line">                <span class="keyword">else</span></span><br><span class="line">                    <span class="comment">// 生成的代码功能等价于下面的代码：</span></span><br><span class="line">                    <span class="comment">//   url.getProtocol() == null ? getNameCode : url.getProtocol()</span></span><br><span class="line">                    <span class="comment">// 以 Protocol 接口的 connect 方法为例，最终生成的代码如下：</span></span><br><span class="line">                    <span class="comment">//   url.getProtocol() == null ? &quot;dubbo&quot; : url.getProtocol()</span></span><br><span class="line">                    getNameCode = String.format(<span class="string">&quot;url.getProtocol() == null ? (%s) : url.getProtocol()&quot;</span>, getNameCode);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 生成 extName 赋值代码</span></span><br><span class="line">        code.append(<span class="string">&quot;\nString extName = &quot;</span>).append(getNameCode).append(<span class="string">&quot;;&quot;</span>);</span><br><span class="line">        <span class="comment">// 生成 extName 判空代码</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> String.format(<span class="string">&quot;\nif(extName == null) &quot;</span> +</span><br><span class="line">                                 <span class="string">&quot;throw new IllegalStateException(\&quot;Fail to get extension(%s) name from url(\&quot; + url.toString() + \&quot;) use keys(%s)\&quot;);&quot;</span>,</span><br><span class="line">                                 type.getName(), Arrays.toString(value));</span><br><span class="line">        code.append(s);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码已经进行了大量的注释，不过看起来任然不是很好理解。既然如此，那么建议大家写点测试代码，对 Protocol、LoadBalance 以及 Transporter 等接口的自适应拓展类代码生成过程进行调试。这里我以 Transporter 接口的自适应拓展类代码生成过程进行分析。首先看一下 Transporter 接口的定义，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SPI(&quot;netty&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Transporter</span> &#123;</span><br><span class="line">    <span class="comment">// @Adaptive(&#123;server, transporter&#125;)</span></span><br><span class="line">    <span class="meta">@Adaptive(&#123;Constants.SERVER_KEY, Constants.TRANSPORTER_KEY&#125;)</span> </span><br><span class="line">    Server <span class="title function_">bind</span><span class="params">(URL url, ChannelHandler handler)</span> <span class="keyword">throws</span> RemotingException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// @Adaptive(&#123;client, transporter&#125;)</span></span><br><span class="line">    <span class="meta">@Adaptive(&#123;Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY&#125;)</span></span><br><span class="line">    Client <span class="title function_">connect</span><span class="params">(URL url, ChannelHandler handler)</span> <span class="keyword">throws</span> RemotingException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面对 connect 方法代理逻辑生成的过程进行分析，此时生成代理逻辑所用到的变量和值如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">defaultExtName</span> <span class="operator">=</span> <span class="string">&quot;netty&quot;</span>;</span><br><span class="line"><span class="type">boolean</span> <span class="variable">hasInvocation</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="type">String</span> <span class="variable">getNameCode</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">String[] value = [<span class="string">&quot;client&quot;</span>, <span class="string">&quot;transporter&quot;</span>];</span><br></pre></td></tr></table></figure><p>下面对 value 数组进行遍历，此时 i &#x3D; 1, value[i] &#x3D; “transporter”，生成的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getNameCode = url.getParameter(<span class="string">&quot;transporter&quot;</span>, <span class="string">&quot;netty&quot;</span>);</span><br></pre></td></tr></table></figure><p>接下来，for 循环继续执行，此时 i &#x3D; 0, value[i] &#x3D; “client”，生成的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">getNameCode = url.getParameter(<span class="string">&quot;client&quot;</span>, url.getParameter(<span class="string">&quot;transporter&quot;</span>, <span class="string">&quot;netty&quot;</span>));</span><br></pre></td></tr></table></figure><p>for 循环结束运行，现在生成 extName 变量及判空代码，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">String</span> <span class="variable">extName</span> <span class="operator">=</span> url.getParameter(<span class="string">&quot;client&quot;</span>, url.getParameter(<span class="string">&quot;transporter&quot;</span>, <span class="string">&quot;netty&quot;</span>));</span><br><span class="line"><span class="keyword">if</span> (extName == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(</span><br><span class="line">        <span class="string">&quot;Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(&quot;</span> + url.toString()</span><br><span class="line">        + <span class="string">&quot;) use keys([client, transporter])&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到此，connect 方法的拓展名获取代码就生成好了。如果大家不是很明白，建议自己调试走一遍。好了，本节先到这里。</p><h5 id="2-2-3-6-生成拓展加载与目标方法调用逻辑"><a href="#2-2-3-6-生成拓展加载与目标方法调用逻辑" class="headerlink" title="2.2.3.6 生成拓展加载与目标方法调用逻辑"></a>2.2.3.6 生成拓展加载与目标方法调用逻辑</h5><p>上一节的逻辑生成拓展名 extName 获取逻辑，接下来要做的是根据拓展名加载拓展实例，并调用拓展实例的目标方法。相关逻辑如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">    Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    Class&lt;?&gt;[] ets = method.getExceptionTypes();</span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// $无 Adaptive 注解方法代码生成&#125;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// $&#123;获取 URL 数据&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;获取 Adaptive 注解值&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;检测 Invocation 参数&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;生成拓展名获取逻辑&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生成拓展获取代码，格式如下：</span></span><br><span class="line">        <span class="comment">// type全限定名 extension = (type全限定名)ExtensionLoader全限定名</span></span><br><span class="line">        <span class="comment">//     .getExtensionLoader(type全限定名.class).getExtension(extName);</span></span><br><span class="line">        <span class="comment">// Tips: 格式化字符串中的 %&lt;s 表示使用前一个转换符所描述的参数，即 type 全限定名</span></span><br><span class="line">        s = String.format(<span class="string">&quot;\n%s extension = (%&lt;s)%s.getExtensionLoader(%s.class).getExtension(extName);&quot;</span>,</span><br><span class="line">                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());</span><br><span class="line">        code.append(s);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果方法有返回值类型非 void，则生成 return 语句。</span></span><br><span class="line">        <span class="keyword">if</span> (!rt.equals(<span class="keyword">void</span>.class)) &#123;</span><br><span class="line">            code.append(<span class="string">&quot;\nreturn &quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生成目标方法调用逻辑，格式为：</span></span><br><span class="line">        <span class="comment">//     extension.方法名(arg0, arg2, ..., argN);</span></span><br><span class="line">        s = String.format(<span class="string">&quot;extension.%s(&quot;</span>, method.getName());</span><br><span class="line">        code.append(s);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (i != <span class="number">0</span>)</span><br><span class="line">                code.append(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">            code.append(<span class="string">&quot;arg&quot;</span>).append(i);</span><br><span class="line">        &#125;</span><br><span class="line">        code.append(<span class="string">&quot;);&quot;</span>);   </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 省略无关逻辑</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以 Protocol 接口举例说明，上面代码生成的内容如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">com.alibaba.dubbo.rpc.<span class="type">Protocol</span> <span class="variable">extension</span> <span class="operator">=</span> (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader</span><br><span class="line">            .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);</span><br><span class="line"><span class="keyword">return</span> extension.refer(arg0, arg1);</span><br></pre></td></tr></table></figure><h5 id="2-2-3-7-生成完整的方法"><a href="#2-2-3-7-生成完整的方法" class="headerlink" title="2.2.3.7 生成完整的方法"></a>2.2.3.7 生成完整的方法</h5><p>本节进行代码生成的收尾工作，主要用于生成方法定义的代码。相关逻辑如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (Method method : methods) &#123;</span><br><span class="line">    Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    Class&lt;?&gt;[] ets = method.getExceptionTypes();</span><br><span class="line"></span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">512</span>);</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// $无 Adaptive 注解方法代码生成&#125;</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// $&#123;获取 URL 数据&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;获取 Adaptive 注解值&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;检测 Invocation 参数&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;生成拓展名获取逻辑&#125;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// $&#123;生成拓展加载与目标方法调用逻辑&#125;</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// public + 返回值全限定名 + 方法名 + (</span></span><br><span class="line">codeBuilder.append(<span class="string">&quot;\npublic &quot;</span>)</span><br><span class="line">    .append(rt.getCanonicalName())</span><br><span class="line">    .append(<span class="string">&quot; &quot;</span>)</span><br><span class="line">    .append(method.getName())</span><br><span class="line">    .append(<span class="string">&quot;(&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加参数列表代码</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; i++) &#123;</span><br><span class="line">    <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        codeBuilder.append(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    codeBuilder.append(pts[i].getCanonicalName());</span><br><span class="line">    codeBuilder.append(<span class="string">&quot; &quot;</span>);</span><br><span class="line">    codeBuilder.append(<span class="string">&quot;arg&quot;</span>).append(i);</span><br><span class="line">&#125;</span><br><span class="line">codeBuilder.append(<span class="string">&quot;)&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 添加异常抛出代码</span></span><br><span class="line"><span class="keyword">if</span> (ets.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    codeBuilder.append(<span class="string">&quot; throws &quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; ets.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (i &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            codeBuilder.append(<span class="string">&quot;, &quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        codeBuilder.append(ets[i].getCanonicalName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">codeBuilder.append(<span class="string">&quot; &#123;&quot;</span>);</span><br><span class="line">codeBuilder.append(code.toString());</span><br><span class="line">codeBuilder.append(<span class="string">&quot;\n&#125;&quot;</span>);</span><br></pre></td></tr></table></figure><p>以 Protocol 的 refer 方法为例，上面代码生成的内容如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> com.alibaba.dubbo.rpc.Invoker <span class="title function_">refer</span><span class="params">(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)</span> &#123;</span><br><span class="line">    <span class="comment">// 方法体</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="3-基于动态代理实现知识与拓展"><a href="#3-基于动态代理实现知识与拓展" class="headerlink" title="3.基于动态代理实现知识与拓展"></a>3.基于动态代理实现知识与拓展</h2><p>我在第一章介绍自适应拓展原理时说过，Dubbo 通过生成和编译代码实现自适应拓展的方式有点复杂，不利于维护。另外，这样做对源码学习读者来说，也不是很友好。我敢肯定，有同学会像我一样，在开始调试 Dubbo 源码时，不知道如何调试各种自适应拓展类，比如 Protocol$Adaptive。如果你也有类似的困惑，这里教大家一个方法。如下：</p><ol><li>在 createAdaptiveExtensionClass 方法的第一行打个断点</li><li>启动测试代码，代码运行到端点处，单步越过断点，此时可以得到生成的代码。</li><li>拷贝出刚刚获取到的代码，到指定的包下创建同名类，并将代码拷过去，格式化一下即可</li></ol><p>从调试信息中可知，ProtocolAdaptive 所在包为 com.alibaba.dubbo.rpc。因此接下来到 com.alibaba.dubbo.rpc 包下创建 ProtocolAdaptive 类，并把 code 变量值拷贝到刚创建的文件中。</p><p>既然 Dubbo 实现的 Adaptive 机制不利于调试，那么我们可以对其进行改造。改造后的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdaptiveInvokeHandler</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> String defaultExtName;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AdaptiveInvokeHandler</span><span class="params">(String defaultExtName)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.defaultExtName = defaultExtName;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">getProxy</span><span class="params">(Class clazz)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!clazz.isInterface()) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Only create the proxy for interface.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Proxy.newProxyInstance(<span class="built_in">this</span>.getClass().getClassLoader(), <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;clazz&#125;, <span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line">        Class&lt;?&gt; type = method.getDeclaringClass();</span><br><span class="line">        <span class="keyword">if</span> (type.equals(Object.class)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>(<span class="string">&quot;Cannot invoke the method of Object&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">        <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>(<span class="string">&quot;method &quot;</span> + method.toString() + <span class="string">&quot; of interface &quot;</span> + type.getName() + <span class="string">&quot; is not adaptive method!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取 URL 数据</span></span><br><span class="line">        <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> getUrlData(method, args);</span><br><span class="line">        <span class="comment">// 获取 Adaptive 注解值</span></span><br><span class="line">        String[] value = getAdaptiveAnnotationValue(method);</span><br><span class="line">        <span class="comment">// 获取 Invocation 参数</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">invocation</span> <span class="operator">=</span> getInvocationArgument(method, args);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取拓展名</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">extName</span> <span class="operator">=</span> getExtensionName(url, value, invocation);</span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isEmpty(extName)) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(</span><br><span class="line">                <span class="string">&quot;Fail to get extension(&quot;</span> + type.getName() + <span class="string">&quot;) name from url(&quot;</span> + url.toString()</span><br><span class="line">                    + <span class="string">&quot;) use keys(&quot;</span> + Arrays.toString(value) +<span class="string">&quot;)&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取拓展实例</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">extension</span> <span class="operator">=</span> ExtensionLoader.getExtensionLoader(type).getExtension(extName);</span><br><span class="line">        Class&lt;?&gt; extType = extension.getClass();</span><br><span class="line">        <span class="type">Method</span> <span class="variable">targetMethod</span> <span class="operator">=</span> extType.getMethod(method.getName(), method.getParameterTypes());</span><br><span class="line">        <span class="comment">// 通过反射调用目标方法</span></span><br><span class="line">        <span class="keyword">return</span> targetMethod.invoke(extension, args);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样看起来是不是简单了一些，不过这并不是全部的代码。我将 URL 数据以及 Adaptive 注解值的获取逻辑封装在了私有方法中，相应的代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> URL <span class="title function_">getUrlData</span><span class="params">(Method method, Object[] args)</span> <span class="keyword">throws</span> InvocationTargetException, IllegalAccessException &#123;</span><br><span class="line">    <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (pts[i].equals(URL.class)) &#123;</span><br><span class="line">            url = (URL) args[i];</span><br><span class="line">            <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">urlTypeIndex</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="type">Method</span> <span class="variable">getter</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">        LBL_PTS:</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; ++i) &#123;</span><br><span class="line">            Method[] ms = pts[i].getMethods();</span><br><span class="line">            <span class="keyword">for</span> (Method m : ms) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> m.getName();</span><br><span class="line">                <span class="keyword">if</span> ((name.startsWith(<span class="string">&quot;get&quot;</span>) || name.length() &gt; <span class="number">3</span>)</span><br><span class="line">                    &amp;&amp; Modifier.isPublic(m.getModifiers())</span><br><span class="line">                    &amp;&amp; !Modifier.isStatic(m.getModifiers())</span><br><span class="line">                    &amp;&amp; m.getParameterTypes().length == <span class="number">0</span></span><br><span class="line">                    &amp;&amp; m.getReturnType() == URL.class) &#123;</span><br><span class="line"></span><br><span class="line">                    urlTypeIndex = i;</span><br><span class="line">                    getter = m;</span><br><span class="line">                    <span class="keyword">break</span> LBL_PTS;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (urlTypeIndex == -<span class="number">1</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Cannot find URL argument.&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (args[urlTypeIndex] == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(pts[urlTypeIndex].getName() + <span class="string">&quot; argument == null&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        url = (URL) getter.invoke(args[urlTypeIndex]);</span><br><span class="line">        <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(pts[urlTypeIndex].getName() + <span class="string">&quot; argument &quot;</span> + getter.getName() + <span class="string">&quot;() == null&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> url;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String[] getAdaptiveAnnotationValue(Method method) &#123;</span><br><span class="line">    <span class="type">Adaptive</span> <span class="variable">adaptiveAnnotation</span> <span class="operator">=</span> method.getAnnotation(Adaptive.class);</span><br><span class="line">    <span class="type">Class</span> <span class="variable">type</span> <span class="operator">=</span> method.getDeclaringClass();</span><br><span class="line">    <span class="keyword">if</span> (adaptiveAnnotation == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;method &quot;</span> + method.toString() + <span class="string">&quot; of interface &quot;</span> + type.getName() + <span class="string">&quot; is not adaptive method!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    String[] value = adaptiveAnnotation.value();</span><br><span class="line">    <span class="keyword">if</span> (value.length == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">char</span>[] charArray = type.getSimpleName().toCharArray();</span><br><span class="line">        <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="number">128</span>);</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; charArray.length; i++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (Character.isUpperCase(charArray[i])) &#123;</span><br><span class="line">                <span class="keyword">if</span> (i != <span class="number">0</span>) &#123;</span><br><span class="line">                    sb.append(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                sb.append(Character.toLowerCase(charArray[i]));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                sb.append(charArray[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        value = <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;sb.toString()&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Object <span class="title function_">getInvocationArgument</span><span class="params">(Method method, Object[] args)</span> &#123;</span><br><span class="line">    Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pts.length; ++i) &#123;</span><br><span class="line">        <span class="keyword">if</span> (pts[i].getName().equals(<span class="string">&quot;com.alibaba.dubbo.rpc.Invocation&quot;</span>)) &#123;</span><br><span class="line">            <span class="type">Object</span> <span class="variable">invocation</span> <span class="operator">=</span> args[i];</span><br><span class="line">            <span class="keyword">if</span> (invocation == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;invocation == null&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> invocation;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String <span class="title function_">getExtensionName</span><span class="params">(URL url, String[] value, Invocation invocation)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">methodName</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">hasInvocation</span> <span class="operator">=</span> invocation != <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (hasInvocation) &#123;</span><br><span class="line">        Class&lt;?&gt; clazz = invocation.getClass();</span><br><span class="line">        <span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> clazz.getMethod(<span class="string">&quot;getMethodName&quot;</span>);</span><br><span class="line">        methodName = (String) method.invoke(invocation);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">String</span> <span class="variable">extName</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; value.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="string">&quot;protocol&quot;</span>.equals(value[i])) &#123;</span><br><span class="line">            <span class="keyword">if</span> (hasInvocation) &#123;</span><br><span class="line">                extName = url.getMethodParameter(methodName, value[i], defaultExtName);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                extName = url.getParameter(value[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            extName = url.getProtocol();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isNotEmpty(extName)) &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (i == value.length -<span class="number">1</span> &amp;&amp; StringUtils.isEmpty(extName)) &#123;</span><br><span class="line">            extName = defaultExtName;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> extName;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在我们将 AdaptiveInvokeHandler 放置到 ExtensionLoader 所在包下，并对 ExtensionLoader 的 createAdaptiveExtension 方法代码进行改造。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> T <span class="title function_">createAdaptiveExtension</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        getExtensionClasses();</span><br><span class="line">        <span class="type">T</span> <span class="variable">extension</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">if</span> (cachedAdaptiveClass != <span class="literal">null</span>) &#123;</span><br><span class="line">            extension = (T) cachedAdaptiveClass.newInstance();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (extension == <span class="literal">null</span>) &#123;</span><br><span class="line">            extension = (T) <span class="keyword">new</span> <span class="title class_">AdaptiveInvokeHandler</span>(cachedDefaultName).getProxy(type);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> injectExtension(extension);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Can not create adaptive extension &quot;</span> + type + <span class="string">&quot;, cause: &quot;</span> + e.getMessage(), e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上就是改造后的代码，需要特别说明的是，上面的代码仅供演示使用，代码逻辑并不是十分严谨。如果你有更好的写法，欢迎分享。</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h2><p>到此，关于自适应拓展的原理，实现以及改造过程就分析完了。总的来说自适应拓展整个逻辑还是很复杂的，并不是很容易弄懂。因此，大家在阅读该部分源码时，耐心一些，同时多进行调试。亦或是通过生成好的代码思考生成逻辑。当然，大家也可以将代码生成逻辑看成一个黑盒，不懂细节也没关系，只要知道自适应拓展原理即可。</p><p>好了，本篇文章先到这里，感谢大家的阅读。</p></article><div class="post-copyright"><div class="copyright-cc-box"><i class="anzhiyufont anzhiyu-icon-copyright"></i></div><div class="post-copyright__author_box"><a class="post-copyright__author_img" href="/" title="头像"><img class="post-copyright__author_img_back" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"><img class="post-copyright__author_img_front" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"></a><div class="post-copyright__author_name">云少</div><div class="post-copyright__author_desc">站在巨人的肩膀罢了</div></div><div class="post-copyright__post__info"><a class="post-copyright__original" title="该文章为原创文章，注意版权协议" href="https://it985.github.io/posts/cf451fe.html">原创</a><a class="post-copyright-title"><span onclick='rm.copyPageUrl("https://it985.github.io/posts/cf451fe.html")'>Dubbo 源码分析 – 自适应拓展原理</span></a></div><div class="post-tools" id="post-tools"><div class="post-tools-left"><div class="rewardLeftButton"><div class="post-reward" onclick="anzhiyu.addRewardMask()"><div class="reward-button button--animated" title="赞赏作者"><i class="anzhiyufont anzhiyu-icon-hand-heart-fill"></i>打赏作者</div><div class="reward-main"><div class="reward-all"><span class="reward-title">感谢你赐予我前进的力量</span><ul class="reward-group"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/wxpay.webp" alt="wechat"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/alipay.webp" alt="alipay"></a><div class="post-qr-code-desc">alipay</div></li></ul><a class="reward-main-btn" href="/about/#about-reward" target="_blank"><div class="reward-text">赞赏者名单</div><div class="reward-dec">因为你们的支持让我意识到写文章的价值🙏</div></a></div></div></div><div id="quit-box" onclick="anzhiyu.removeRewardMask()" style="display:none"></div></div><div class="shareRight"><div class="share-link mobile"><div class="share-qrcode"><div class="share-button" title="使用手机访问这篇文章"><i class="anzhiyufont anzhiyu-icon-qrcode"></i></div><div class="share-main"><div class="share-main-all"><div id="qrcode" title="https://it985.github.io/posts/cf451fe.html"></div><div class="reward-dec">使用手机访问这篇文章</div></div></div></div></div><div class="share-link weibo"><a class="share-button" target="_blank" href="https://service.weibo.com/share/share.php?title=Dubbo 源码分析 – 自适应拓展原理&amp;url=https://it985.github.io/posts/cf451fe.html&amp;pic=https://www.bing.com/th?id=OHR.SeaAngel_EN-IN2839358434_UHD.jpg" rel="external nofollow noreferrer noopener"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a></div><div class="share-link copyurl"><div class="share-button" id="post-share-url" title="复制链接" onclick="rm.copyPageUrl()"><i class="anzhiyufont anzhiyu-icon-link"></i></div></div></div></div></div><div class="post-copyright__notice"><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://it985.github.io" target="_blank">云少IT</a>！</span></div></div><div class="post-tools-right"><div class="tag_share"><div class="post-meta__box"><div class="post-meta__box__tag-list"></div></div><div class="post_share"><div class="social-share" data-image="https://img02.anheyu.com/adminuploads/1/2022/09/05/6315e146a8bbd.webp" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/css/share.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/js/social-share.min.js" defer></script></div></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/30e5cc.html"><img class="prev-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.xjh.me/random_img.php?type=bg&amp;ctype=nature&amp;return=302&amp;_r_=95659137-236a-4972-4031-20818b4d27e8" onerror='onerror=null,src="/img/404.jpg"' alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">专栏汇总：Dubbo 源码分析</div></div></a></div><div class="next-post pull-right"><a href="/posts/bc8dae89.html"><img class="next-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.BadLightning_EN-GB7102993068_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Dubbo 源码分析 – 服务导出</div></div></a></div></nav><hr><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="anzhiyufont anzhiyu-icon-comments"></i><span> 评论</span></div><div class="comment-randomInfo"><a onclick="anzhiyu.addRandomCommentInfo()" href="javascript:void(0)" rel="external nofollow noreferrer">匿名评论</a><a href="/privacy" style="margin-left:4px">隐私政策</a></div><div class="comment-switch"><span class="first-comment">Twikoo</span><span id="switch-btn"></span><span class="second-comment">Artalk</span></div><div class="comment-tips" id="comment-tips"><span>✅ 你无需删除空行，直接评论以获取最佳展示效果</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div><div><div id="artalk-wrap"></div></div></div></div><div class="comment-barrage"></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-bars"></i><span>文章目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E4%BA%8C%E3%80%81Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E2%80%93-%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95%E5%8E%9F%E7%90%86"><span class="toc-number">1.</span> <span class="toc-text">二、Dubbo 源码分析 – 自适应拓展原理</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E5%8E%9F%E7%90%86"><span class="toc-number">1.1.</span> <span class="toc-text">1.原理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">1.2.</span> <span class="toc-text">2.源码分析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-%E8%8E%B7%E5%8F%96%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95"><span class="toc-number">1.2.1.</span> <span class="toc-text">2.1 获取自适应拓展</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-%E8%87%AA%E9%80%82%E5%BA%94%E6%8B%93%E5%B1%95%E7%B1%BB%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90"><span class="toc-number">1.2.2.</span> <span class="toc-text">2.2 自适应拓展类代码生成</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-1-Adaptive-%E6%B3%A8%E8%A7%A3%E6%A3%80%E6%B5%8B"><span class="toc-number">1.2.2.1.</span> <span class="toc-text">2.2.1 Adaptive 注解检测</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-2-%E7%94%9F%E6%88%90%E7%B1%BB"><span class="toc-number">1.2.2.2.</span> <span class="toc-text">2.2.2 生成类</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2-3-%E7%94%9F%E6%88%90%E6%96%B9%E6%B3%95"><span class="toc-number">1.2.2.3.</span> <span class="toc-text">2.2.3 生成方法</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-1-%E6%97%A0-Adaptive-%E6%B3%A8%E8%A7%A3%E6%96%B9%E6%B3%95%E4%BB%A3%E7%A0%81%E7%94%9F%E6%88%90"><span class="toc-number">1.2.2.3.1.</span> <span class="toc-text">2.2.3.1 无 Adaptive 注解方法代码生成</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-2-%E8%8E%B7%E5%8F%96-URL-%E6%95%B0%E6%8D%AE"><span class="toc-number">1.2.2.3.2.</span> <span class="toc-text">2.2.3.2 获取 URL 数据</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-3-%E8%8E%B7%E5%8F%96-Adaptive-%E6%B3%A8%E8%A7%A3%E5%80%BC"><span class="toc-number">1.2.2.3.3.</span> <span class="toc-text">2.2.3.3 获取 Adaptive 注解值</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-4-%E6%A3%80%E6%B5%8B-Invocation-%E5%8F%82%E6%95%B0"><span class="toc-number">1.2.2.3.4.</span> <span class="toc-text">2.2.3.4 检测 Invocation 参数</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-5-%E7%94%9F%E6%88%90%E6%8B%93%E5%B1%95%E5%90%8D%E8%8E%B7%E5%8F%96%E9%80%BB%E8%BE%91"><span class="toc-number">1.2.2.3.5.</span> <span class="toc-text">2.2.3.5 生成拓展名获取逻辑</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-6-%E7%94%9F%E6%88%90%E6%8B%93%E5%B1%95%E5%8A%A0%E8%BD%BD%E4%B8%8E%E7%9B%AE%E6%A0%87%E6%96%B9%E6%B3%95%E8%B0%83%E7%94%A8%E9%80%BB%E8%BE%91"><span class="toc-number">1.2.2.3.6.</span> <span class="toc-text">2.2.3.6 生成拓展加载与目标方法调用逻辑</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#2-2-3-7-%E7%94%9F%E6%88%90%E5%AE%8C%E6%95%B4%E7%9A%84%E6%96%B9%E6%B3%95"><span class="toc-number">1.2.2.3.7.</span> <span class="toc-text">2.2.3.7 生成完整的方法</span></a></li></ol></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E5%9F%BA%E4%BA%8E%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E5%AE%9E%E7%8E%B0%E7%9F%A5%E8%AF%86%E4%B8%8E%E6%8B%93%E5%B1%95"><span class="toc-number">1.3.</span> <span class="toc-text">3.基于动态代理实现知识与拓展</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-%E6%80%BB%E7%BB%93"><span class="toc-number">1.4.</span> <span class="toc-text">4.总结</span></a></li></ol></li></ol></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="footer_deal"><a class="deal_link" href="mailto:2071916845@qq.com" rel="external nofollow noreferrer" title="email"><i class="anzhiyufont anzhiyu-icon-envelope"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://weibo.com/" title="微博"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.facebook.com/" title="facebook"><i class="anzhiyufont anzhiyu-icon-facebook1"></i></a><a class="deal_link" href="/atom.xml" title="RSS"><i class="anzhiyufont anzhiyu-icon-rss"></i></a><img class="footer_mini_logo" title="返回顶部" alt="返回顶部" onclick="anzhiyu.scrollToDest(0,500)" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" size="50px"><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/it985" title="Github"><i class="anzhiyufont anzhiyu-icon-github"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://space.bilibili.com/300767383" title="Bilibili"><i class="anzhiyufont anzhiyu-icon-bilibili"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://v.douyin.com/" title="抖音"><i class="anzhiyufont anzhiyu-icon-tiktok"></i></a><a class="deal_link" href="/copyright" title="CC"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i></a></div><div id="anzhiyu-footer"><div class="footer-group"><div class="footer-title">服务</div><div class="footer-links"><a class="footer-item" title="51la统计" target="_blank" rel="noopener external nofollow noreferrer" href="https://v6.51.la/">51la统计</a><a class="footer-item" title="十年之约" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.foreverblog.cn/">十年之约</a><a class="footer-item" title="开往" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/travellings-link/travellings">开往</a></div></div><div class="footer-group"><div class="footer-title">导航</div><div class="footer-links"><a class="footer-item" title="即刻短文" href="/essay/">即刻短文</a><a class="footer-item" title="友链文章" href="/fcircle/">友链文章</a><a class="footer-item" title="留言板" href="/comments/">留言板</a></div></div><div class="footer-group"><div class="footer-title">协议</div><div class="footer-links"><a class="footer-item" title="免责声明" href="/disclaimer/">免责声明</a><a class="footer-item" title="隐私协议" href="/privacy/">隐私协议</a><a class="footer-item" title="Cookies" href="/cookies/">Cookies</a><a class="footer-item" title="版权协议" href="/copyright/">版权协议</a></div></div><div class="footer-group"><div class="footer-title">娱乐</div><div class="footer-links"><a class="footer-item" title="小空调" href="/air-conditioner/">小空调</a><a class="footer-item" title="围住小猫" href="/catch-cat/">围住小猫</a><a class="footer-item" title="免费图床" href="/huluxiapicture/">免费图床</a></div></div><div class="footer-group"><div class="footer-title">推荐分类</div><div class="footer-links"><a class="footer-item" title="Java" href="/categories/Java/">Java</a><a class="footer-item" title="面试" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></div></div><div class="footer-group"><div class="footer-title-group"><div class="footer-title">友链</div><a class="random-friends-btn" id="footer-random-friends-btn" href="javascript:addFriendLinksInFooter();" rel="external nofollow noreferrer" title="换一批友情链接"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i></a></div><div class="footer-links" id="friend-links-in-footer"></div></div></div><div id="workboard"><img class="workSituationImg boardsign" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg" alt="距离月入25k也就还差一个大佬带我~" title="距离月入25k也就还差一个大佬带我~"><div id="runtimeTextTip"></div></div><div class="wordcount"></div><span>云少已经写了 996.7k 字，</span><span>好像写完一本我国著名的 四大名著 了！！！</span><p id="ghbdages"><a class="github-badge" target="_blank" href="https://hexo.io/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="博客框架为Hexo_v5.4.0" title="博客框架为Hexo_v5.4.0"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg" alt="博客框架为Hexo_v5.4.0"></a><a class="github-badge" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站使用又拍云为静态资源提供CDN加速" title="本站使用又拍云为静态资源提供CDN加速"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/CDN-%E5%8F%88%E6%8B%8D%E4%BA%91-orange%3Fstyle%3Dflat%26logo%3D%E5%8F%88%E6%8B%8D%E4%BA%91" alt="本站使用又拍云为静态资源提供CDN加速"></a><a class="github-badge" target="_blank" href="https://github.com/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站项目由Github托管" title="本站项目由Github托管"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg" alt="本站项目由Github托管"></a><a class="github-badge" target="_blank" href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可" title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg" alt="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"></a><a class="github-badge" target="_blank" href="https://icp.gov.moe/?keyword=20221607" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="萌ICP备20221607号" title="萌ICP备20221607号"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/%E8%90%8CICP%E5%A4%87-20221607-fe1384?style-flat&amp;logo=" alt="萌ICP备20221607号"></a></p></div><div id="footer-bar"><div class="footer-bar-links"><div class="footer-bar-left"><div id="footer-bar-tips"><div class="copyright">&copy;2018 - 2023 By <a class="footer-bar-link" href="/" title="云少" target="_blank">云少</a></div></div><div id="footer-type-tips"></div><div class="js-pjax"><script>function subtitleType(){fetch("https://v1.hitokoto.cn").then(t=>t.json()).then(t=>{var e="出自 "+t.from,p=[];p.unshift(t.hitokoto,e),window.typed=new Typed("#footer-type-tips",{strings:p,startDelay:300,typeSpeed:150,loop:!0,backSpeed:50})})}"function"==typeof Typed?subtitleType():getScript("https://cdn.cbd.int/typed.js@2.0.15/dist/typed.umd.js").then(subtitleType)</script></div></div><div class="footer-bar-right"><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="云少">云少</a><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://beian.miit.gov.cn/" title="鄂ICP备2021021095号-2">鄂ICP备2021021095号-2</a><a class="footer-bar-link cc" href="/copyright" title="cc协议"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-by-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nc-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nd-line"></i></a></div></div></div></footer></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="sidebar-site-data site-data is-center"><a href="/archives/" title="archive"><div class="headline">文章</div><div class="length-num">861</div></a><a href="/tags/" title="tag"><div class="headline">标签</div><div class="length-num">35</div></a><a href="/categories/" title="category"><div class="headline">分类</div><div class="length-num">6</div></a></div><span class="sidebar-menu-item-title">功能</span><div class="sidebar-menu-item"><a class="darkmode_switchbutton menu-child" href="javascript:void(0);" rel="external nofollow noreferrer" title="显示模式"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span>显示模式</span></a></div><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div><span class="sidebar-menu-item-title">标签</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:.88rem;color:#758692">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:.88rem;color:#2584bf">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:.88rem;color:#bfad3e">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:.88rem;color:#b9b609">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:.88rem;color:#14c83c">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:.88rem;color:#0a1878">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:.88rem;color:#625421">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:.88rem;color:#aa71bf">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:.88rem;color:#ad8774">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:.88rem;color:#19704c">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:.88rem;color:#7e5c78">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:.88rem;color:#700769">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:.88rem;color:#2b8671">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:.88rem;color:#1b0694">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:.88rem;color:#36202d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:.88rem;color:#643f76">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:.88rem;color:#1e4c38">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:.88rem;color:#a5a097">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:.88rem;color:#05682d">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:.88rem;color:#38ad2a">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:.88rem;color:#827a6b">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:.88rem;color:#4a3ac7">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:.88rem;color:#b89c14">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:.88rem;color:#922401">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:.88rem;color:#437d09">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:.88rem;color:#705b06">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:.88rem;color:#06a544">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:.88rem;color:#c86c4f">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:.88rem;color:#560b2c">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:.88rem;color:#4e0a32">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:.88rem;color:#230f8e">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:.88rem;color:#c0771e">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:.88rem;color:#b40caa">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:.88rem;color:#67389a">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:.88rem;color:#57425c">面试<sup>11</sup></a></div></div><hr></div></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="anzhiyufont anzhiyu-icon-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="anzhiyufont anzhiyu-icon-gear"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="anzhiyufont anzhiyu-icon-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="anzhiyufont anzhiyu-icon-comments"></i></a><button type="button" title="切换背景" onclick="toggleWinbox()"><i class="fas fa-display"></i></button><button id="go-up" type="button" title="回到顶部"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><button class="search-close-button"><i class="anzhiyufont anzhiyu-icon-xmark"></i></button></nav><div class="search-wrap"><div id="algolia-search-input"></div><hr><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-info"><div class="algolia-stats"></div><div class="algolia-poweredBy"></div></div></div></div></div><div id="search-mask"></div></div><div id="rightMenu"><div class="rightMenu-group rightMenu-small"><div class="rightMenu-item" id="menu-backward"><i class="anzhiyufont anzhiyu-icon-arrow-left"></i></div><div class="rightMenu-item" id="menu-forward"><i class="anzhiyufont anzhiyu-icon-arrow-right"></i></div><div class="rightMenu-item" id="menu-refresh"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right" style="font-size:1rem"></i></div><div class="rightMenu-item" id="menu-top"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></div></div><div class="rightMenu-group rightMenu-line rightMenuPlugin"><div class="rightMenu-item" id="menu-copytext"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制选中文本</span></div><div class="rightMenu-item" id="menu-pastetext"><i class="anzhiyufont anzhiyu-icon-paste"></i><span>粘贴文本</span></div><a class="rightMenu-item" id="menu-commenttext"><i class="anzhiyufont anzhiyu-icon-comment-medical"></i><span>引用到评论</span></a><div class="rightMenu-item" id="menu-newwindow"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开</span></div><div class="rightMenu-item" id="menu-copylink"><i class="anzhiyufont anzhiyu-icon-link"></i><span>复制链接地址</span></div><div class="rightMenu-item" id="menu-copyimg"><i class="anzhiyufont anzhiyu-icon-images"></i><span>复制此图片</span></div><div class="rightMenu-item" id="menu-downloadimg"><i class="anzhiyufont anzhiyu-icon-download"></i><span>下载此图片</span></div><div class="rightMenu-item" id="menu-newwindowimg"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开图片</span></div><div class="rightMenu-item" id="menu-search"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>站内搜索</span></div><div class="rightMenu-item" id="menu-searchBaidu"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>百度搜索</span></div><div class="rightMenu-item" id="menu-music-toggle"><i class="anzhiyufont anzhiyu-icon-play"></i><span>播放音乐</span></div><div class="rightMenu-item" id="menu-music-back"><i class="anzhiyufont anzhiyu-icon-backward"></i><span>切换到上一首</span></div><div class="rightMenu-item" id="menu-music-forward"><i class="anzhiyufont anzhiyu-icon-forward"></i><span>切换到下一首</span></div><div class="rightMenu-item" id="menu-music-playlist" onclick="window.open(&quot;https://y.qq.com/n/ryqq/playlist/8802438608&quot;, &quot;_blank&quot;);" style="display:none"><i class="anzhiyufont anzhiyu-icon-radio"></i><span>查看所有歌曲</span></div><div class="rightMenu-item" id="menu-music-copyMusicName"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制歌名</span></div></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item menu-link" id="menu-randomPost"><i class="anzhiyufont anzhiyu-icon-shuffle"></i><span>随便逛逛</span></a><a class="rightMenu-item menu-link" href="/categories/"><i class="anzhiyufont anzhiyu-icon-cube"></i><span>博客分类</span></a><a class="rightMenu-item menu-link" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags"></i><span>文章标签</span></a></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item" id="menu-copy" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制地址</span></a><a class="rightMenu-item" id="menu-commentBarrage" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-message"></i><span class="menu-commentBarrage-text">关闭热评</span></a><a class="rightMenu-item" id="menu-darkmode" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span class="menu-darkmode-text">深色模式</span></a><a class="rightMenu-item" id="menu-translate" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-language"></i><span>轉為繁體</span></a></div></div><div id="rightmenu-mask"></div><div id="he-plugin-simple"></div><script>var WIDGET={CONFIG:{modules:"0124",background:"2",tmpColor:"FFFFFF",tmpSize:"16",cityColor:"FFFFFF",citySize:"16",aqiColor:"E8D87B",aqiSize:"16",weatherIconSize:"24",alertIconSize:"18",padding:"10px 10px 10px 10px",shadow:"0",language:"auto",borderRadius:"20",fixed:"true",vertical:"top",horizontal:"left",left:"20",top:"7.1",key:"df245676fb434a0691ead1c63341cd94"}}</script><link rel="stylesheet" href="https://widget.qweather.net/simple/static/css/he-simple.css?v=1.4.0"><script src="https://widget.qweather.net/simple/static/js/he-simple.js?v=1.4.0"></script><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.umd.js"></script><script src="https://cdn.cbd.int/instant.page@5.2.0/instantpage.js" type="module"></script><script src="https://cdn.cbd.int/vanilla-lazyload@17.8.4/dist/lazyload.iife.min.js"></script><script src="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.js"></script><script>function panguFn(){"object"==typeof pangu?pangu.autoSpacingPage():getScript("https://cdn.cbd.int/pangu@4.0.7/dist/browser/pangu.min.js").then(()=>{pangu.autoSpacingPage()})}function panguInit(){panguFn()}document.addEventListener("DOMContentLoaded",panguInit)</script><script>var meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r"</script><canvas id="universe"></canvas><script async src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/dark/dark.js"></script><script>var HoldLog=console.log;console.log=function(){};let now1=new Date;queueMicrotask(()=>{function o(){HoldLog.apply(console,arguments)}var c=new Date("09/01/2018 00:00:00"),c=(now1.setTime(now1.getTime()+250),(now1-c)/1e3/60/60/24),c=["欢迎使用安知鱼!","生活明朗, 万物可爱",`
        
       █████╗ ███╗   ██╗███████╗██╗  ██╗██╗██╗   ██╗██╗   ██╗
      ██╔══██╗████╗  ██║╚══███╔╝██║  ██║██║╚██╗ ██╔╝██║   ██║
      ███████║██╔██╗ ██║  ███╔╝ ███████║██║ ╚████╔╝ ██║   ██║
      ██╔══██║██║╚██╗██║ ███╔╝  ██╔══██║██║  ╚██╔╝  ██║   ██║
      ██║  ██║██║ ╚████║███████╗██║  ██║██║   ██║   ╚██████╔╝
      ╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝    ╚═════╝
        
        `,"已上线",Math.floor(c),"天","©2018 By 安知鱼 V1.6.6"],e=["NCC2-036","调用前置摄像头拍照成功，识别为【小笨蛋】.","Photo captured: ","🤪"];setTimeout(o.bind(console,`
%c${c[0]} %c ${c[1]} %c ${c[2]} %c${c[3]}%c ${c[4]}%c ${c[5]}

%c ${c[6]}
`,"color:#425AEF","","color:#425AEF","color:#425AEF","","color:#425AEF","")),setTimeout(o.bind(console,`%c ${e[0]} %c ${e[1]} %c 
${e[2]} %c
${e[3]}
`,"color:white; background-color:#4fd953","","",'background:url("https://npm.elemecdn.com/anzhiyu-blog@1.1.6/img/post/common/tinggge.gif") no-repeat;font-size:450%')),setTimeout(o.bind(console,"%c WELCOME %c 你好，小笨蛋.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c ⚡ Powered by 安知鱼 %c 你正在访问 云少 的博客.","color:white; background-color:#f0ad4e","")),setTimeout(o.bind(console,"%c W23-12 %c 你已打开控制台.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c S013-782 %c 你现在正处于监控中.","color:white; background-color:#d9534f",""))})</script><script async src="/anzhiyu/random.js"></script><script async>!function(){var n,o,r,a,i,e=new Date("09/01/2018 00:00:00"),l=new Date;setInterval(()=>{var t;if(l=new Date,i=l.getHours(),t=(l-e)/1e3/60/60/24,n=Math.floor(t),t=(l-e)/1e3/60/60-24*n,o=Math.floor(t),1==String(o).length&&(o="0"+o),t=(l-e)/1e3/60-1440*n-60*o,r=Math.floor(t),1==String(r).length&&(r="0"+r),t=(l-e)/1e3-86400*n-3600*o-60*r,a=Math.round(t),1==String(a).length&&(a="0"+a),document.getElementById("footer")){let e="";e=(i<18&&9<=i||((t=document.querySelector("#workboard .workSituationImg")).src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg",t.title="下班了就该开开心心的玩耍，嘿嘿~",t.alt="下班了就该开开心心的玩耍，嘿嘿~"),`本站居然运行了 ${n} 天<span id='runtime'> ${o} 小时 ${r} 分 ${a} 秒 </span><i class='anzhiyufont anzhiyu-icon-heartbeat' style='color:red'></i>`),document.getElementById("runtimeTextTip")&&(document.getElementById("runtimeTextTip").innerHTML=e)}},1e3)}()</script><script src="https://cdn.cbd.int/algoliasearch@4.18.0/dist/algoliasearch-lite.umd.js"></script><script src="https://cdn.cbd.int/instantsearch.js@4.56.5/dist/instantsearch.production.min.js"></script><script src="/js/search/algolia.js"></script><div class="js-pjax"><script>(()=>{const e=document.querySelectorAll("#article-container .mermaid-wrap");if(0!==e.length){const n=()=>{window.loadMermaid=!0;const a="dark"===document.documentElement.getAttribute("data-theme")?"dark":"default";Array.from(e).forEach((e,t)=>{const n=e.firstElementChild;e="%%{init:{ 'theme':'"+a+"'}}%%\n"+n.textContent;const d=mermaid.render("mermaid-"+t,e);"string"==typeof d?(t=d,n.insertAdjacentHTML("afterend",t)):d.then(({svg:e})=>{n.insertAdjacentHTML("afterend",e)})})};var t=()=>{window.loadMermaid?n():getScript("https://cdn.cbd.int/mermaid@10.2.4/dist/mermaid.min.js").then(n)};anzhiyu.addGlobalFn("themeChange",n,"mermaid"),window.pjax?t():document.addEventListener("DOMContentLoaded",t)}})()</script><script>(()=>{const t=()=>{twikoo.init(Object.assign({el:"#twikoo-wrap",envId:"https://twikoo.tryrun.top/",region:"",onCommentLoaded:()=>{anzhiyu.loadLightbox(document.querySelectorAll("#twikoo .tk-content img:not(.tk-owo-emotion)"))}},null))};var o=()=>{"object"==typeof twikoo?setTimeout(n,0):getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(n)};const n=()=>{t()};anzhiyu.loadComment(document.getElementById("twikoo-wrap"),o)})()</script><script>(()=>{const t=()=>{window.artalkItem=new Artalk(Object.assign({el:"#artalk-wrap",server:"https://artalk.tryrun.top/",site:"https://blog.tryrun.top/",pageKey:location.pathname,darkMode:"dark"===document.documentElement.getAttribute("data-theme"),countEl:".artalk-count"},null)),"null"!==GLOBAL_CONFIG.lightbox&&window.artalkItem.use(t=>{t.on("list-loaded",()=>{t.getCommentList().forEach(t=>{t=t.getRender().$content;anzhiyu.loadLightbox(t.querySelectorAll("img:not([atk-emoticon])"))})})})};var a=async()=>{"object"==typeof window.artalkItem||(await getCSS("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.css"),await getScript("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.js")),t()};anzhiyu.addGlobalFn("themeChange",t=>{var a=document.getElementById("artalk-wrap");a&&a.children.length&&window.artalkItem.setDarkMode("dark"===t)},"artalk"),window.loadOtherComment=a})()</script><input type="hidden" name="page-type" id="page-type" value="post"></div><script>window.addEventListener("load",()=>{const t=e=>e=""!==e&&150<(e=(e=(e=(e=e.replace(/<img.*?src="(.*?)"?[^\>]+>/gi,"[图片]")).replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,"[链接]")).replace(/<pre><code>.*?<\/pre>/gi,"[代码]")).replace(/<[^>]+>/g,"")).length?e.substring(0,150)+"...":e,n=t=>{let n="";if(t.length)for(let e=0;e<t.length;e++)n=(n=(n+="<div class='aside-list-item'>")+`<a href='${t[e].url}' class='thumbnail'><img data-lazy-src='${t[e].avatar}' alt='${t[e].nick}'><div class='name'><span>${t[e].nick} </span></div></a>`)+`<div class='content'>
        <a class='comment' href='${t[e].url}' title='${t[e].content}'>${t[e].content}</a>
        <time datetime="${t[e].date}">${anzhiyu.diffDate(t[e].date,!0)}</time></div>
        </div>`;else n+="没有评论";var e=document.querySelector("#card-newest-comments .aside-list");e.innerHTML=n,window.lazyLoadInstance&&window.lazyLoadInstance.update(),window.pjax&&window.pjax.refresh(e)};var e=()=>{var e;document.querySelector("#card-newest-comments .aside-list")&&((e=saveToLocal.get("twikoo-newest-comments"))?n(JSON.parse(e)):(e=()=>{twikoo.getRecentComments({envId:"https://twikoo.tryrun.top/",region:"",pageSize:6,includeReply:!0}).then(function(e){e=e.map(e=>({content:t(e.comment),avatar:e.avatar,nick:e.nick,url:e.url+"#"+e.id,date:new Date(e.created).toISOString()}));saveToLocal.set("twikoo-newest-comments",JSON.stringify(e),10/1440),n(e)}).catch(function(e){document.querySelector("#card-newest-comments .aside-list").textContent="无法获取评论，请确认相关配置是否正确"})},"object"==typeof twikoo?e():getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(e)))};e(),document.addEventListener("pjax:complete",e)})</script><script async data-pjax src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.1/bubble/bubble.js"></script><script>var visitorMail="visitor@anheyu.com"</script><script async data-pjax src="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/waterfall/waterfall.js"></script><script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qrcodejs/1.0.0/qrcode.min.js"></script><script src="/js/anzhiyu/right_click_menu.js"></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.9/icon/ali_iconfont_css.css"><script async src="/js/1.min.js?1"></script><script async src="/js/bg.js?1"></script><script async src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/js/lunar.min.js?1"></script><script async src="/js/day.min.js?1"></script><script async src="/js/winbox.bundle.min.js?1"></script><script id="click-show-text" src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,自由,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/aplayer/APlayer.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/anzhiyu-blog-static@1.0.1/js/APlayer.min.js"></script><script src="https://cdn.cbd.int/hexo-anzhiyu-music@1.0.1/assets/js/Meting2.min.js"></script><script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script><script>let pjaxSelectors=['meta[property="og:image"]','meta[property="og:title"]','meta[property="og:url"]','meta[property="og:type"]','meta[property="og:site_name"]','meta[property="og:description"]',"head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"];var pjax=new Pjax({elements:'a:not([target="_blank"])',selectors:pjaxSelectors,cacheBust:!1,analytics:!0,scrollRestoration:!1});document.addEventListener("pjax:send",function(){if(anzhiyu.removeGlobalFnEvent("pjax"),anzhiyu.removeGlobalFnEvent("themeChange"),document.getElementById("rightside").classList.remove("rightside-show"),window.aplayers)for(let e=0;e<window.aplayers.length;e++)window.aplayers[e].options.fixed||window.aplayers[e].destroy();"object"==typeof typed&&typed.destroy();var e=document.body.classList;e.contains("read-mode")&&e.remove("read-mode")}),document.addEventListener("pjax:complete",function(){window.refreshFn(),document.querySelectorAll("script[data-pjax]").forEach(e=>{const t=document.createElement("script");var a=e.text||e.textContent||e.innerHTML||"";Array.from(e.attributes).forEach(e=>t.setAttribute(e.name,e.value)),t.appendChild(document.createTextNode(a)),e.parentNode.replaceChild(t,e)}),GLOBAL_CONFIG.islazyload&&window.lazyLoadInstance.update(),"function"==typeof chatBtnFn&&chatBtnFn(),"function"==typeof panguInit&&panguInit(),"function"==typeof gtag&&gtag("config","G-3VMKW5TZBM",{page_path:window.location.pathname}),"object"==typeof _hmt&&_hmt.push(["_trackPageview",window.location.pathname]),"function"==typeof loadMeting&&document.getElementsByClassName("aplayer").length&&loadMeting(),"object"==typeof Prism&&Prism.highlightAll()}),document.addEventListener("pjax:error",e=>{404===e.request.status&&pjax.loadUrl("/404.html")})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script charset="UTF-8" src="https://cdn.cbd.int/anzhiyu-theme-static@1.1.5/accesskey/accesskey.js"></script></div><div id="popup-window"><div class="popup-window-title">通知</div><div class="popup-window-divider"></div><div class="popup-window-content"><div class="popup-tip">你好呀</div><div class="popup-link"><i class="anzhiyufont anzhiyu-icon-arrow-circle-right"></i></div></div></div></body></html>